1052 lines
35 KiB
C++
1052 lines
35 KiB
C++
/*++
|
|
|
|
Copyright (c) 1998 Microsoft Corporation
|
|
|
|
Module Name:
|
|
|
|
blockmgr.h
|
|
|
|
Abstract:
|
|
|
|
This module contains the definition of the block memory manager
|
|
|
|
Author:
|
|
|
|
Keith Lau (keithlau@microsoft.com)
|
|
|
|
Revision History:
|
|
|
|
keithlau 02/27/98 created
|
|
|
|
--*/
|
|
|
|
#ifndef __BLOCKMGR_H__
|
|
#define __BLOCKMGR_H__
|
|
|
|
#include "rwnew.h"
|
|
|
|
#include "cpoolmac.h"
|
|
#include "mailmsg.h"
|
|
|
|
//
|
|
// Nasty forwards for these interfaces ...
|
|
//
|
|
struct IMailMsgPropertyStream;
|
|
struct IMailMsgNotify;
|
|
|
|
/***************************************************************************/
|
|
// Define this to remove contention control
|
|
//
|
|
// #define BLOCKMGR_DISABLE_ATOMIC_FUNCS
|
|
// #define BLOCKMGR_DISABLE_CONTENTION_CONTROL
|
|
//
|
|
#ifdef BLOCKMGR_DISABLE_CONTENTION_CONTROL
|
|
#define BLOCKMGR_DISABLE_ATOMIC_FUNCS
|
|
#endif
|
|
|
|
/***************************************************************************/
|
|
// Debug stuff ...
|
|
//
|
|
|
|
#ifdef DEBUG
|
|
|
|
#define DEBUG_TRACK_ALLOCATION_BOUNDARIES
|
|
|
|
#endif
|
|
|
|
/***************************************************************************/
|
|
// CBlockManager - Implementation of pseudo flat memory space using a
|
|
// heap that works like an I-Node. The underlying memory utilizes
|
|
// disjoint, fixed-size memory blocks.
|
|
//
|
|
// Each node is as follows:
|
|
//
|
|
// +---------------------------------------------------------+
|
|
// | Pointers to other nodes | Space for arbitrary data |
|
|
// +---------------------------------------------------------+
|
|
//
|
|
// Analysis:
|
|
// We assume in some way or another memory allocation is based on
|
|
// 4K pages or some multiple thereof. The first thing we want to
|
|
// determine is how many pointers to other nodes we want to have in
|
|
// the node (order of the heap). We know that each node would probably
|
|
// be some size between 1K to 2K so that we won't waste space in the
|
|
// average case of small email messages, yet provide scalability for
|
|
// huge email messages that potentially may have millions of email
|
|
// addresses. 2 intuitive candidates are 32 and 64 pointers.
|
|
//
|
|
// We consider the worst case scenario of MSN, which has about 2.5
|
|
// million users. Assuming the averace recipient record is about
|
|
// 45 bytes (name & attributes, etc.), then we need 112.5M of
|
|
// storage, which is about 2 ^ 27. Assume the average data payload
|
|
// is 1K per node (2 ^ 10), then we need 2 ^ 17 nodes. Thus, for
|
|
// 23 pointers (2 ^ 5) per node, we need 4 layers to cover the
|
|
// required address space of 112M. However, this turns out to be
|
|
// an overkill since 4 layers covers 1G (2 ^ 20) where we only need
|
|
// about 10% of that. As for the 64 pointers case, we only need 3
|
|
// layers to cover 256M (2 ^ (18 + 10)), which roughly covers 5
|
|
// million users. We will choose 64 pointer per node (256 bytes).
|
|
//
|
|
// As for the size of the payload, I considered using 1K allocations
|
|
// and using the remaining 768 bytes as data payload. But since this
|
|
// is not a round power of two, it would be expensive to do a
|
|
// div and mod operation. As an alternative, I suggest allocating
|
|
// 1024 + 256 byte blocks. This makes both numbers round powers of
|
|
// two, which makes div and mod operations simple AND and SHR
|
|
// operations, which typically take 2-4 cycles to complete. Also,
|
|
// when the wasted space is considered, turns out that a 4K page
|
|
// fits 3 such blocks, and only 256 bytes is wasted per page. This
|
|
// comes to 93.3% utilization of space.
|
|
//
|
|
// So each node would look like this:
|
|
// +---------------------------------------------------------+
|
|
// | 64 pointers = 256 bytes | 1K block for arbitrary data |
|
|
// +---------------------------------------------------------+
|
|
//
|
|
// It is an explicit objective that a typical mail message header
|
|
// fits in a single block. Each block fans out to 64 other blocks
|
|
// and each block's payload maps to 1K of the flat data address
|
|
// space. The root node maps to the first 1K of the data space
|
|
// (i.e. absolute address 0 to 1023 in data space), then each
|
|
// of the next 64 nodes in the next layer represents the next 64K,
|
|
// respectively, and so on for each subsequent layer. Nodes for
|
|
// the next layer is not created until the current layer is
|
|
// depleted. Collpasing of nodes is not required due to the fact
|
|
// that the heap can only grow.
|
|
//
|
|
// During commit of the whole heap, the scatter-gather list is
|
|
// built by traversing the entire heap. The average number of
|
|
// dereferences is n*(log64(n))/2).
|
|
//
|
|
// All items in a message object is allocated off this heap.
|
|
//
|
|
// An slight modification can be used to track dirty or unused
|
|
// bits. We can actually add a block of flags and attributes to
|
|
// each node to track dirty regions and other flags. This will
|
|
// probably not be implemented in the initial implementation,
|
|
// but such capability will be factored in. In terms of allocation
|
|
// optimization, we can have a block of up to 64 bytes without
|
|
// disrupting the 4K page allocation scheme. In fact, adding a
|
|
// 64-byte block to each node boosts memory utilization to up
|
|
// to 98.4% without any real extra cost while still keeping each
|
|
// node 64-byte aligned.
|
|
//
|
|
// Synchronization:
|
|
// Allocation of memory in the data space is done through a
|
|
// reservation model where multiple threads can concurrently
|
|
// reserve memory and be guaranteed to get a unique block.
|
|
// A lightweight critical section is used to synchronize block
|
|
// creation should the reservation span into blocks that are
|
|
// not yet allocated. Allocation of new blocks is serialized.
|
|
//
|
|
// Synchronization for concurrent access to the same data space
|
|
// must be enforced at a higher level, if desired.
|
|
//
|
|
|
|
// Define the constants chosen for this implementation
|
|
|
|
#ifdef _WIN64
|
|
// The order will be 5 bits in 64-bit (8 * 32 = 256 bytes)
|
|
#define BLOCK_HEAP_ORDER_BITS (5)
|
|
#else
|
|
// The order will be 6 bits in 32-bit (4 * 64 = 256 bytes)
|
|
#define BLOCK_HEAP_ORDER_BITS (6)
|
|
#endif
|
|
|
|
#define BLOCK_HEAP_ORDER (1 << BLOCK_HEAP_ORDER_BITS)
|
|
#define BLOCK_HEAP_ORDER_MASK (BLOCK_HEAP_ORDER - 1)
|
|
#define BLOCK_HEAP_PAYLOAD_BITS (10)
|
|
#define BLOCK_HEAP_PAYLOAD (1 << BLOCK_HEAP_PAYLOAD_BITS)
|
|
#define BLOCK_HEAP_PAYLOAD_MASK (BLOCK_HEAP_PAYLOAD - 1)
|
|
#define BLOCK_DWORD_ALIGN_MASK (sizeof(DWORD) - 1)
|
|
|
|
#define BLOCK_DEFAULT_FLAGS (BLOCK_IS_DIRTY)
|
|
|
|
#define BLOCK_MAX_ALLOWED_LINEAR_HOPS 3
|
|
|
|
// Define the underlying data type for a flat address in the
|
|
// linear address space, and the type that we use to count nodes.
|
|
// This is for scalability so when we want to use 64-bit
|
|
// quantities, we can simply replace this section of data-size
|
|
// specific values
|
|
//
|
|
// Note: you need to make sure that the data size is AT LEAST:
|
|
// 1 + (BLOCK_HEAP_ORDER_BITS * MAX_HEAP_DEPTH) + BLOCK_HEAP_PAYLOAD_BITS
|
|
//
|
|
// Note: In order for this type to be used as the base address
|
|
// type, the following operations must be supported:
|
|
// - Assignment
|
|
// - Comparison
|
|
// - Arithmetic operators
|
|
// - Bitwise operators
|
|
// - Interlocked operations
|
|
//
|
|
// Start data-size-specific values
|
|
|
|
typedef SIZE_T HEAP_BASE_ADDRESS_TYPE;
|
|
typedef HEAP_BASE_ADDRESS_TYPE HEAP_NODE_ID;
|
|
typedef HEAP_NODE_ID *LPHEAP_NODE_ID;
|
|
typedef HEAP_BASE_ADDRESS_TYPE FLAT_ADDRESS;
|
|
typedef FLAT_ADDRESS *LPFLAT_ADDRESS;
|
|
|
|
// These must be changed if HEAP_BASE_ADDRESS_TYPE is not DWORD
|
|
#define NODE_ID_MAPPING_FACTOR \
|
|
(HEAP_BASE_ADDRESS_TYPE)( \
|
|
1 | \
|
|
(1 << BLOCK_HEAP_ORDER_BITS) | \
|
|
(1 << (BLOCK_HEAP_ORDER_BITS * 2)) \
|
|
)
|
|
// And so on, etc ...
|
|
// (1 << (BLOCK_HEAP_ORDER_BITS * 3))
|
|
// (1 << (BLOCK_HEAP_ORDER_BITS * 4))
|
|
|
|
#define NODE_ID_ABSOLUTE_MAX \
|
|
(HEAP_BASE_ADDRESS_TYPE)( \
|
|
(1 << BLOCK_HEAP_ORDER_BITS) | \
|
|
(1 << (BLOCK_HEAP_ORDER_BITS * 2)) |\
|
|
(1 << (BLOCK_HEAP_ORDER_BITS * 3)) \
|
|
)
|
|
// And so on, etc ...
|
|
// (1 << (BLOCK_HEAP_ORDER_BITS * 4))
|
|
// (1 << (BLOCK_HEAP_ORDER_BITS * 5))
|
|
|
|
#define NODE_ID_BORROW_BIT \
|
|
(HEAP_BASE_ADDRESS_TYPE)(1 << (BLOCK_HEAP_ORDER_BITS * 3))
|
|
|
|
// Depth of heap allowed by base data type
|
|
#define MAX_HEAP_DEPTH 4
|
|
|
|
// Node Id space mask
|
|
#define MAX_FLAT_ADDRESS \
|
|
(FLAT_ADDRESS)((1 << (MAX_HEAP_DEPTH * BLOCK_HEAP_ORDER_BITS)) - 1)
|
|
|
|
// Same as a NULL pointer
|
|
#define INVALID_FLAT_ADDRESS ((FLAT_ADDRESS)-1)
|
|
|
|
// Number of bits to rotate the mapped result
|
|
#define NODE_ID_ROR_FACTOR ((MAX_HEAP_DEPTH - 1) * BLOCK_HEAP_ORDER_BITS)
|
|
|
|
// Define the rotate functions
|
|
#define ROTATE_LEFT(v, n) _lrotl((v), (n))
|
|
#define ROTATE_RIGHT(v, n) _lrotr((v), (n))
|
|
|
|
// Define the interlocked functions
|
|
#define AtomicAdd(pv, a) \
|
|
(HEAP_BASE_ADDRESS_TYPE)InterlockedExchangeAdd((long *)(pv), (a))
|
|
|
|
// End data-size-specific values
|
|
|
|
// Forward declaration of the _BLOCK_HEAP_NODE structure
|
|
struct _BLOCK_HEAP_NODE;
|
|
|
|
// Define the attribute block for each node
|
|
typedef struct _BLOCK_HEAP_NODE_ATTRIBUTES
|
|
{
|
|
struct _BLOCK_HEAP_NODE *pParentNode; // Pointer to parent node
|
|
HEAP_NODE_ID idChildNode; // Which child am I?
|
|
HEAP_NODE_ID idNode; // Id of node in block heap
|
|
FLAT_ADDRESS faOffset; // Starting offset the node
|
|
DWORD fFlags; // Attributes of the block
|
|
|
|
#ifdef DEBUG_TRACK_ALLOCATION_BOUNDARIES
|
|
// This tracks the allocation boundaries between memory
|
|
// allocations so that we can check whether a read or write
|
|
// crosses an allocation boundary. We use a bit to represent
|
|
// the start of a block. Since the allocations are DWORD-aligned,
|
|
// we need BLOCK_HEAP_PAYLOAD >> 2 >> 3 bits to track
|
|
// all allocation boundaries per block.
|
|
BYTE rgbBoundaries[BLOCK_HEAP_PAYLOAD >> 5];
|
|
#endif
|
|
|
|
} BLOCK_HEAP_NODE_ATTRIBUTES, *LPBLOCK_HEAP_NODE_ATTRIBUTES;
|
|
|
|
// Define each node in the heap
|
|
typedef struct _BLOCK_HEAP_NODE
|
|
{
|
|
struct _BLOCK_HEAP_NODE *rgpChildren[BLOCK_HEAP_ORDER];
|
|
BLOCK_HEAP_NODE_ATTRIBUTES stAttributes;
|
|
BYTE rgbData[BLOCK_HEAP_PAYLOAD];
|
|
|
|
} BLOCK_HEAP_NODE, *LPBLOCK_HEAP_NODE;
|
|
|
|
#define BLOCK_HEAP_NODE_SIZE (sizeof(BLOCK_HEAP_NODE))
|
|
|
|
#define BOP_LOCK_ACQUIRED 0x80000000
|
|
#define BOP_NO_BOUNDARY_CHECK 0x40000000
|
|
#define BOP_OPERATION_MASK 0x0000ffff
|
|
|
|
typedef enum _BLOCK_OPERATION_CODES
|
|
{
|
|
BOP_READ = 0,
|
|
BOP_WRITE
|
|
|
|
} BLOCK_OPERATION_CODES;
|
|
|
|
// Define the block attribute flags
|
|
#define BLOCK_IS_DIRTY 0x00000001
|
|
#define BLOCK_PENDING_COMMIT 0x00000002
|
|
|
|
// block allocation flags
|
|
// the block was allocated with CMemoryAccess instead of cpool
|
|
#define BLOCK_NOT_CPOOLED 0x00010000
|
|
|
|
#define BLOCK_CLEAN_MASK (~(BLOCK_IS_DIRTY))
|
|
#define RESET_BLOCK_FLAGS(_flags_) _flags_ &= 0xffff0000
|
|
#define DEFAULT_BLOCK_FLAGS(_flags_) _flags_ &= (0xffff0000 | BLOCK_IS_DIRTY)
|
|
|
|
//
|
|
// Define a method signature for acquiring a stream pointer
|
|
//
|
|
typedef IMailMsgPropertyStream *(*PFN_STREAM_ACCESSOR)(LPVOID);
|
|
|
|
/***************************************************************************/
|
|
// Context class for memory access
|
|
//
|
|
|
|
class CBlockContext
|
|
{
|
|
private:
|
|
|
|
DWORD m_dwSignature;
|
|
|
|
public:
|
|
|
|
CBlockContext() { Invalidate(); }
|
|
~CBlockContext() { Invalidate(); }
|
|
|
|
BOOL IsValid();
|
|
|
|
void Set(
|
|
LPBLOCK_HEAP_NODE pLastAccessedNode,
|
|
FLAT_ADDRESS faLastAccessedNodeOffset
|
|
);
|
|
|
|
void Invalidate();
|
|
|
|
LPBLOCK_HEAP_NODE m_pLastAccessedNode;
|
|
FLAT_ADDRESS m_faLastAccessedNodeOffset;
|
|
};
|
|
|
|
|
|
/***************************************************************************/
|
|
// Memory allocator classes
|
|
//
|
|
|
|
class CBlockMemoryAccess
|
|
{
|
|
public:
|
|
|
|
CBlockMemoryAccess() {}
|
|
~CBlockMemoryAccess() {}
|
|
|
|
HRESULT AllocBlock(
|
|
LPVOID *ppvBlock,
|
|
DWORD dwBlockSize
|
|
);
|
|
|
|
HRESULT FreeBlock(
|
|
LPVOID pvBlock
|
|
);
|
|
|
|
//
|
|
// CPool
|
|
//
|
|
static CPool m_Pool;
|
|
};
|
|
|
|
|
|
class CMemoryAccess
|
|
{
|
|
public:
|
|
|
|
CMemoryAccess() {}
|
|
~CMemoryAccess() {}
|
|
|
|
static HRESULT AllocBlock(
|
|
LPVOID *ppvBlock,
|
|
DWORD dwBlockSize
|
|
);
|
|
|
|
static HRESULT FreeBlock(
|
|
LPVOID pvBlock
|
|
);
|
|
};
|
|
|
|
/***************************************************************************/
|
|
// Class for accessing stream
|
|
//
|
|
|
|
class CBlockManagerGetStream
|
|
{
|
|
public:
|
|
virtual HRESULT GetStream(
|
|
IMailMsgPropertyStream **ppStream,
|
|
BOOL fLockAcquired
|
|
) = 0;
|
|
};
|
|
|
|
|
|
/***************************************************************************/
|
|
// Block heap manager
|
|
//
|
|
|
|
class CBlockManager
|
|
{
|
|
public:
|
|
CBlockManager(
|
|
IMailMsgProperties *pMsg,
|
|
CBlockManagerGetStream *pParent = NULL
|
|
);
|
|
~CBlockManager();
|
|
|
|
// Sanity check
|
|
BOOL IsValid();
|
|
|
|
// This initializes an empty MailMsg to a certain size.
|
|
// CAUTION: This should only be used to initialize an empty MailMsg
|
|
// when binding to a non-empty stream. Any other uses will cause
|
|
// unpredictable results and/or corruption or even crashes.
|
|
HRESULT SetStreamSize(
|
|
DWORD dwStreamSize
|
|
);
|
|
|
|
//
|
|
// Synopsis:
|
|
// Allocate the desired amount of memory.
|
|
// Thread safe.
|
|
//
|
|
// Arguments:
|
|
// dwSizeDesired - the size of the block desired
|
|
// pfaOffsetToReservedMemory - returns the offset to the
|
|
// reserved block of memory, if successful, in the
|
|
// flat memory space managed by the block manager.
|
|
// pdwSizeAllocated - returns the actual size allocated, which
|
|
// is greater than or equal to the desired size, if successful.
|
|
// pContext (Optional) - fills in a context that describes
|
|
// the reserved block. This context can be used in
|
|
// subsequent reads and writes to the block. Accesses
|
|
// using this context are faster than using the
|
|
// offset alone. Ignored if NULL. The caller must allocate
|
|
// the context structure prior to calling ReserveMemory.
|
|
//
|
|
// Return values:
|
|
// S_OK - Success, the memory of requested size is
|
|
// successfully reserved.
|
|
// STG_E_INSUFFICIENTMEMORY - Error, the required amount of memory
|
|
// is not available to honor the request.
|
|
// STG_E_INVALIDPARAMETER - Internal error, mostly used
|
|
// for debug considerations.
|
|
//
|
|
HRESULT AllocateMemory(
|
|
DWORD dwSizeDesired,
|
|
FLAT_ADDRESS *pfaOffsetToAllocatedMemory,
|
|
DWORD *pdwSizeAllocated,
|
|
CBlockContext *pContext // Optional
|
|
);
|
|
|
|
//
|
|
// Synopsis:
|
|
// Returns the total size allocated by this block manager.
|
|
// Thread safe.
|
|
//
|
|
// Arguments:
|
|
// pfaSizeAllocated - returns the total size allocated.
|
|
//
|
|
// Return values:
|
|
// S_OK - Success, the memory of requested size is
|
|
// successfully reserved.
|
|
// STG_E_INVALIDPARAMETER - Internal error, mostly used
|
|
// for debug considerations.
|
|
//
|
|
HRESULT GetAllocatedSize(
|
|
FLAT_ADDRESS *pfaSizeAllocated
|
|
);
|
|
|
|
//
|
|
// Synopsis:
|
|
// Reads a chunk of contiguous memory in flat address space into a
|
|
// user-supplied buffer. Synchronization not supported at this level.
|
|
//
|
|
// Arguments:
|
|
// pbBuffer - buffer to return contents read, must be large enough
|
|
// to store the data read.
|
|
// faTargetOffset - offset measured in flat address space to start
|
|
// reading from.
|
|
// dwBytesToRead - number of contiguous bytes to read
|
|
// pdwBytesRead - returns number of bytes actually read
|
|
// pContext (Optional) - if specified, uses an alternate optimized
|
|
// algorithm to access the memory, otherwise, the system looks
|
|
// up the node in question using a full lookup, which is slower.
|
|
// The system decides which algorithm to use based on some heuristics.
|
|
//
|
|
// Return values:
|
|
// S_OK - Success, the read is successful.
|
|
// STG_E_INVALIDPARAMETER - Error, one or more parameters are invalid, or
|
|
// otherwise inconsistent.
|
|
// STG_E_READFAULT - Error, The read failed to complete, pdwBytesRead
|
|
// reflects the actual number of bytes read into pbBuffer.
|
|
// TYPE_E_OUTOFBOUNDS - Debug Error, a read is issued to read past
|
|
// the current allocated block.
|
|
//
|
|
HRESULT ReadMemory(
|
|
LPBYTE pbBuffer,
|
|
FLAT_ADDRESS faTargetOffset,
|
|
DWORD dwBytesToRead,
|
|
DWORD *pdwBytesRead,
|
|
CBlockContext *pContext // Optional
|
|
);
|
|
|
|
//
|
|
// Synopsis:
|
|
// Writes a chunk of contiguous memory from a specified buffer into
|
|
// a specified offset in the flat address space. Synchronization not
|
|
// supported at this level.
|
|
//
|
|
// Arguments:
|
|
// pbBuffer - source buffer of bytes to be written
|
|
// faTargetOffset - offset measured in flat address space to start
|
|
// writing to.
|
|
// dwBytesToWrite - number of contiguous bytes to write
|
|
// pdwBytesWritten - returns number of bytes actually written
|
|
// pContext (Optional) - if specified, uses an alternate optimized
|
|
// algorithm to access the memory, otherwise, the system looks
|
|
// up the node in question using a full lookup, which is slower.
|
|
// The system decides which algorithm to use based on some heuristics.
|
|
//
|
|
// Return values:
|
|
// S_OK - Success, the read is successful.
|
|
// STG_E_INVALIDPARAMETER - Error, one or more parameters are invalid, or
|
|
// otherwise inconsistent.
|
|
// STG_E_WRITEFAULT - Error, The read failed to complete, pdwBytesRead
|
|
// reflects the actual number of bytes read into pbBuffer.
|
|
// TYPE_E_OUTOFBOUNDS - Debug Error, a write is issued to write past
|
|
// the current allocated block.
|
|
//
|
|
HRESULT WriteMemory(
|
|
LPBYTE pbBuffer,
|
|
FLAT_ADDRESS faTargetOffset,
|
|
DWORD dwBytesToWrite,
|
|
DWORD *pdwBytesWritten,
|
|
CBlockContext *pContext // Optional
|
|
);
|
|
|
|
//
|
|
// Synopsis:
|
|
// Copies a chunk of contiguous memory of a specified size from a specified
|
|
// starting offset into the same starting offset of the target address space
|
|
// managed by the target block manager. Synchronization is not supported at this
|
|
// level.
|
|
//
|
|
// Note that a copy is special in that it can cross allocation boundaries.
|
|
//
|
|
// Arguments:
|
|
// faOffset - offset measured in flat address space to start copying.
|
|
// dwBytesToCopy - number of contiguous bytes to copy.
|
|
// pTargetBlockManager - Target block manager whose address space to
|
|
// copy into.
|
|
//
|
|
// Return values:
|
|
// S_OK - Success, the copy is successful.
|
|
// STG_E_INVALIDPARAMETER - Error, one or more parameters are invalid, or
|
|
// otherwise inconsistent.
|
|
// STG_E_READFAULT - Error, a read failed to complete, the copy is not
|
|
// comleted.
|
|
// STG_E_WRITEFAULT - Error, a write failed to complete, the copy is not
|
|
// comleted.
|
|
//
|
|
HRESULT CopyTo(
|
|
FLAT_ADDRESS faOffset,
|
|
DWORD dwBytesToCopy,
|
|
CBlockManager *pTargetBlockManager,
|
|
BOOL fLockAcquired = FALSE
|
|
);
|
|
|
|
//
|
|
// Synopsis:
|
|
// Atomically reads the length and size of a data block, and loads the
|
|
// data block from the offset of the size specified.
|
|
//
|
|
// Arguments:
|
|
// pbBuffer - target buffer of bytes to write the read data
|
|
// pdwBufferSize - Contains the length of the supplied buffer going in,
|
|
// and returns the length of data actually read.
|
|
// pbInfoStruct - Structure containing the information structure
|
|
// faOffsetToInfoStruct - Offset to the info structure
|
|
// dwSizeOfInfoStruct - Size of the info struct to load
|
|
// dwOffsetInInfoStructToOffset - Offset to the address of the data block.
|
|
// this is measured w.r.t. the info structure
|
|
// dwOffsetInInfoStructToOffset - Offset to the size of the data block.
|
|
// this is measured w.r.t. the info structure
|
|
// pContext (Optional) - if specified, uses an alternate optimized
|
|
// algorithm to access the memory, otherwise, the system looks
|
|
// up the node in question using a full lookup, which is slower.
|
|
// The system decides which algorithm to use based on some heuristics.
|
|
//
|
|
// Return values:
|
|
// S_OK - Success, the read is successful.
|
|
// STG_E_INVALIDPARAMETER - Error, one or more parameters are invalid, or
|
|
// otherwise inconsistent.
|
|
// HRESULT_FROM_WIN32(ERROR_INSUFFICIENT_BUFFER) - Error/Informational,
|
|
// the supplied buffer is not large enough to hold all the data.
|
|
// *pdwBufferSize returns the actual number of bytes read.
|
|
// STG_E_READFAULT - Error, The read failed to complete.
|
|
// TYPE_E_OUTOFBOUNDS - Debug Error, a read is issued to read past
|
|
// the current allocated block.
|
|
//
|
|
HRESULT AtomicDereferenceAndRead(
|
|
LPBYTE pbBuffer,
|
|
DWORD *pdwBufferSize,
|
|
LPBYTE pbInfoStruct,
|
|
FLAT_ADDRESS faOffsetToInfoStruct,
|
|
DWORD dwSizeOfInfoStruct,
|
|
DWORD dwOffsetInInfoStructToOffset,
|
|
DWORD dwOffsetInInfoStructToSize,
|
|
CBlockContext *pContext // Optional
|
|
);
|
|
|
|
//
|
|
// Synopsis:
|
|
// Atomically writes the contents of a buffer to memory in flat space and
|
|
// increments a DWORD value by a specified amount. The write is attempted
|
|
// first, and if it succeeds, the value is incremented. If the write fails
|
|
// for some reason, the value will not be incremented. This is to ensure that
|
|
// all the data is written before the increment so the data "exists" by the
|
|
// time the counter is updated.
|
|
//
|
|
// Arguments:
|
|
// pbBuffer - source buffer of bytes to be written
|
|
// faOffset - offset measured in flat address space to start
|
|
// writing to.
|
|
// dwBytesToWrite - number of contiguous bytes to write
|
|
// pdwValueToIncrement - Pointer to the value to be atomically incremented
|
|
// after the write successfully written. If this value is NULL, the
|
|
// increment is ignored and only a protected write is performed.
|
|
// dwReferenceValue - If the value in pdwValueToIncrement differs from this
|
|
// value, the call will be aborted.
|
|
// dwIncrementValue - Amount to increment pdwValueToIncrement.
|
|
// pContext (Optional) - if specified, uses an alternate optimized
|
|
// algorithm to access the memory, otherwise, the system looks
|
|
// up the node in question using a full lookup, which is slower.
|
|
// The system decides which algorithm to use based on some heuristics.
|
|
//
|
|
// Return values:
|
|
// S_OK - Success, the write is successful.
|
|
// HRESULT_FROM_WIN32(ERROR_RETRY) - Informational, The reference value
|
|
// changed during processing and the call cannot complete. A retry
|
|
// should be performed immediately.
|
|
// STG_E_INVALIDPARAMETER - Error, one or more parameters are invalid, or
|
|
// otherwise inconsistent.
|
|
// STG_E_WRITEFAULT - Error, The write failed to complete.
|
|
// TYPE_E_OUTOFBOUNDS - Debug Error, a write is issued to write past
|
|
// the current allocated block.
|
|
//
|
|
HRESULT AtomicWriteAndIncrement(
|
|
LPBYTE pbBuffer,
|
|
FLAT_ADDRESS faOffset,
|
|
DWORD dwBytesToWrite,
|
|
DWORD *pdwValueToIncrement,
|
|
DWORD dwReferenceValue,
|
|
DWORD dwIncrementValue,
|
|
CBlockContext *pContext // Optional
|
|
);
|
|
|
|
//
|
|
// Synopsis:
|
|
// Atomically allocates memory, writes the contents of a buffer to memory
|
|
// in flat space and increments a DWORD value by a specified amount. The
|
|
// allocation is preceeded by a synchronization object and the allocation
|
|
// takes place only if the value of the value to increment is identical before
|
|
// and after the synchronization object is acquired. This allows multiple threads
|
|
// to call this function for the same base object and only one such allocation
|
|
// will succeed. The user can specify a buffer containing content data that will
|
|
// be copied to the allocated buffer should the allocation succeed.
|
|
// There can be 3 outcomes from the allocation:
|
|
// 1) Allocation succeeded
|
|
// 2) Allocation failed due to memory system problems
|
|
// 3) Allocation was not done because the increment value changed during the
|
|
// acquisition of the synchronization object.
|
|
//
|
|
// If the allocation failed due to memory problems, this function will fail without
|
|
// performing the rest of the duties. For scenario 1, the function will
|
|
// continue. For scenario 3, the function will return a specific error code
|
|
// indicating that it had been beaten and the caller will have to do something else
|
|
//
|
|
// After the allocation phase, the write is attempted first, and if it succeeds,
|
|
// the value is incremented. If the write fails for some reason, the value will
|
|
// not be incremented. This is to ensure that all the data is written before the
|
|
// increment so the data "exists" by the time the counter is updated. On the
|
|
// event of a write failure, the memory cannot be salvaged.
|
|
//
|
|
// Arguments:
|
|
// dwDesiredSize - Size of memory block to allocate
|
|
// pfaOffsetToAllocatedMemory - returns the starting offset to the
|
|
// allocated block, in flat address space
|
|
// faOffsetToWriteOffsetToAllocatedMemory - Specifies a location in
|
|
// which to store the offset of the allocated block
|
|
// faOffsetToWriteSizeOfAllocatedMemory - Specifies a location in
|
|
// which to store the actual size of the allocated block
|
|
// pbInitialValueForAllocatedMemory - Specifies a buffer that contains
|
|
// the initial value for the allocated block. This will be copied
|
|
// to the allocated block if the allocation succeeds.
|
|
// pbBufferToWriteFrom - source buffer of bytes to be written
|
|
// dwOffsetInAllocatedMemoryToWriteTo - offset from the start of the
|
|
// allocated block to start writing to.
|
|
// dwSizeofBuffer - number of contiguous bytes to write
|
|
// pdwValueToIncrement - Pointer to the value to be atomically incremented
|
|
// after the write successfully written. This value MUST NOT be NULL.
|
|
// dwReferenceValue - If the value in pdwValueToIncrement differs from this
|
|
// value, the call will be aborted.
|
|
// dwIncrementValue - Amount to increment pdwValueToIncrement.
|
|
// pContext (Optional) - if specified, uses an alternate optimized
|
|
// algorithm to access the memory, otherwise, the system looks
|
|
// up the node in question using a full lookup, which is slower.
|
|
// The system decides which algorithm to use based on some heuristics.
|
|
//
|
|
// Return values:
|
|
// S_OK - Success, the write is successful.
|
|
// HRESULT_FROM_WIN32(ERROR_RETRY) - Informational, The reference value
|
|
// changed during processing and the call cannot complete. A retry
|
|
// should be performed immediately.
|
|
// STG_E_INVALIDPARAMETER - Error, one or more parameters are invalid, or
|
|
// otherwise inconsistent.
|
|
// STG_E_WRITEFAULT - Error, The write failed to complete.
|
|
// TYPE_E_OUTOFBOUNDS - Debug Error, a write is issued to write past
|
|
// the current allocated block.
|
|
//
|
|
HRESULT AtomicAllocWriteAndIncrement(
|
|
DWORD dwDesiredSize,
|
|
FLAT_ADDRESS *pfaOffsetToAllocatedMemory,
|
|
FLAT_ADDRESS faOffsetToWriteOffsetToAllocatedMemory,
|
|
FLAT_ADDRESS faOffsetToWriteSizeOfAllocatedMemory,
|
|
LPBYTE pbInitialValueForAllocatedMemory,
|
|
DWORD dwSizeOfInitialValue,
|
|
LPBYTE pbBufferToWriteFrom,
|
|
DWORD dwOffsetInAllocatedMemoryToWriteTo,
|
|
DWORD dwSizeofBuffer,
|
|
DWORD *pdwValueToIncrement,
|
|
DWORD dwReferenceValue,
|
|
DWORD dwIncrementValue,
|
|
CBlockContext *pContext // Optional
|
|
);
|
|
|
|
//
|
|
// Synopsis:
|
|
// Traverses the list of allocated blocks, from the specified address, and
|
|
// finds dirty blocks. For each dirty block encountered, the block will be
|
|
// changed from "DIRTY" to "PENDING COMMIT". The flat address offset, block
|
|
// size, and memory pointer to that block will be stored in the pdwOffset,
|
|
// pdwSize, and ppbData arrays, respectively. An optional faLengthToScan
|
|
// specifies the number of bytes from the starting offset to scan for
|
|
// dirty blocks, if this is INVALID_FLAT_ADDRESS, then this function scans
|
|
// to the end of all allocated blocks. It is not an error if there are
|
|
// less allocated bytes than the length specified, only the allocated blocks
|
|
// are scanned.
|
|
//
|
|
// The number of elements in each of these arrays is specified therough
|
|
// pdwCount, which returns the number of dirty pages returned.
|
|
//
|
|
// When this function is first called, a strat address should be specified
|
|
// (e.g. 0). When the function returns, pContext will be filled with the
|
|
// next block to start traversing the next time this function is called.
|
|
// Subsequent calls should pass in INVALID_FLAT_ADDRESS for start address
|
|
// and use the pContext previously returned.
|
|
//
|
|
// Arguments:
|
|
// faStartingOffset - Starting offset to start scanning for dirty blocks
|
|
// dwLengthToScan - Length of memory from start to scan for dirty blocks
|
|
// pdwCount - Specifies the number of entries allocated for each of the
|
|
// offset, size and pointer arrays. Returns the number of dirty
|
|
// pages actually found.
|
|
// pdwOffset - Array to hold the offsets of pages
|
|
// pdwSize - Array to hold the sizes of pages
|
|
// ppbData - Array to hold the memory pointer to each page
|
|
// pContext - Causes an alternate optimized algorithm to be used to access
|
|
// the memory, otherwise, the system looks up the node in question using
|
|
// a full lookup, which is slower. The system decides which algorithm to
|
|
// use based on some heuristics.
|
|
//
|
|
// Return values:
|
|
// S_OK - Success, one or more dirty blocks are returned.
|
|
// HRESULT_FROM_WIN32(ERROR_NO_MORE_ITEMS) - Informational, no more dirty
|
|
// blocks are found from the starting address to the end of the list.
|
|
// STG_E_INVALIDPARAMETER - Error, one or more parameters are invalid, or
|
|
// otherwise inconsistent.
|
|
//
|
|
HRESULT BuildDirtyBlockList(
|
|
FLAT_ADDRESS faStartingOffset,
|
|
FLAT_ADDRESS faLengthToScan,
|
|
DWORD *pdwCount,
|
|
FLAT_ADDRESS *pfaOffset,
|
|
DWORD *pdwSize,
|
|
LPBYTE *ppbData,
|
|
CBlockContext *pContext
|
|
);
|
|
|
|
//
|
|
// Synopsis:
|
|
// Walks the list of allocated blocks ans changes all blocks whose state
|
|
// is "PENDING COMMIT" to "CLEAN" or "DIRTY".
|
|
//
|
|
// In the debug version, any block that is both "DIRTY" and "PENDING COMMIT"
|
|
// is invalid and results in an ASSERT.
|
|
//
|
|
// Arguments:
|
|
// fClean - TRUE to mark the blocks as "CLEAN", FALSE for "DIRTY"
|
|
//
|
|
// Return values:
|
|
// S_OK - Success.
|
|
//
|
|
HRESULT MarkAllPendingBlocks(
|
|
BOOL fClean
|
|
);
|
|
|
|
//
|
|
// Synopsis:
|
|
// Sets the state of a specified block to the specified state.
|
|
//
|
|
// In the debug version, any block that is both "DIRTY" and "PENDING COMMIT"
|
|
// is invalid and results in an ASSERT.
|
|
//
|
|
// Arguments:
|
|
// pbData - block as specified by its data pointer
|
|
// fClean - TRUE to mark the blocks as "CLEAN", FALSE for "DIRTY"
|
|
//
|
|
// Return values:
|
|
// S_OK - Success.
|
|
//
|
|
HRESULT MarkBlockAs(
|
|
LPBYTE pbData,
|
|
BOOL fClean
|
|
);
|
|
|
|
//
|
|
// Synopsis:
|
|
// Traverses the list of allocated blocks, from the specified address, and
|
|
// finds dirty blocks. For each dirty block encountered, the block will be
|
|
// changed from "DIRTY" to "PENDING COMMIT" and the block will marked for
|
|
// commit. When enough of these blocks are encountered, they will be
|
|
// committed in a batch and the committed blocks will be marked as "CLEAN".
|
|
// The process will iterate until no more dirty blocks. An optional faLengthToScan
|
|
// specifies the number of bytes from the starting offset to scan for
|
|
// dirty blocks, if this is INVALID_FLAT_ADDRESS, then this function scans
|
|
// to the end of all allocated blocks. It is not an error if there are
|
|
// less allocated bytes than the length specified, only the allocated blocks
|
|
// are scanned.
|
|
//
|
|
// Arguments:
|
|
// faStartingOffset - Starting offset to start scanning for dirty blocks
|
|
// dwLengthToScan - Length of memory from start to scan for dirty blocks
|
|
// pStream - specifies the IMailMsgPropertyStore to use to commit the blocks.
|
|
// fComputeBlockCountsOnly - don't make calls to WriteBlocks, just
|
|
// compute counters for what would be sent to WriteBlocks.
|
|
// pcBlocksToWrite - incremented by how many blocks we would write
|
|
// pcTotalBytesToWrite - incremented by the total byte count of what we
|
|
// would write
|
|
//
|
|
// Return values:
|
|
// S_OK - Success, one or more dirty blocks are returned.
|
|
// STG_E_INVALIDPARAMETER - Error, one or more parameters are invalid, or
|
|
// otherwise inconsistent.
|
|
// Plus the error codomain of IMailMsgPropertyStream
|
|
//
|
|
HRESULT CommitDirtyBlocks(
|
|
FLAT_ADDRESS faStartingOffset,
|
|
FLAT_ADDRESS faLengthToScan,
|
|
DWORD dwFlags,
|
|
IMailMsgPropertyStream *pStream,
|
|
BOOL fDontMarkAsCommit,
|
|
BOOL fComputeBlockCountsOnly,
|
|
DWORD *pcBlocksToWrite,
|
|
DWORD *pcTotalBytesToWrite,
|
|
IMailMsgNotify *pNotify
|
|
);
|
|
|
|
//
|
|
// Synopsis:
|
|
// Releases the entire list of nodes managed by this object.
|
|
//
|
|
// Arguments:
|
|
// None.
|
|
//
|
|
// Return values:
|
|
// S_OK - Success.
|
|
//
|
|
HRESULT Release();
|
|
|
|
//
|
|
// Synopsis:
|
|
// Exposes the lock in the block manager, attempts to access the internal lock
|
|
//
|
|
// Arguments:
|
|
// None.
|
|
//
|
|
// Remarks:
|
|
// These locks will cause deadlocks if a thread tries to acquire it twice.
|
|
// In debug builds, there will be some sort of deadlock detection, in
|
|
// retail, you will deadlock.
|
|
//
|
|
// Return values:
|
|
// S_OK - Success, the lock operation succeeded.
|
|
// !(SUCCESS(HRESULT)) - An error occurred and the lock operaiton failed.
|
|
//
|
|
HRESULT ReadLock() { m_rwLock.ShareLock(); return(S_OK); }
|
|
HRESULT ReadUnlock() { m_rwLock.ShareUnlock(); return(S_OK); }
|
|
HRESULT WriteLock() { m_rwLock.ExclusiveLock(); return(S_OK); }
|
|
HRESULT WriteUnlock() { m_rwLock.ExclusiveUnlock(); return(S_OK); }
|
|
|
|
// return the state of the dirty flag
|
|
BOOL IsDirty() { return m_fDirty; }
|
|
|
|
// change the value of the dirty flag. this is used by MailMsg to
|
|
// set it to FALSE when a successful Commit has occured.
|
|
void SetDirty(BOOL fDirty) {
|
|
m_fDirty = fDirty;
|
|
#ifdef DEBUG
|
|
// _ASSERT(!(m_fCommitting && m_fDirty));
|
|
#endif
|
|
}
|
|
void SetCommitMode(BOOL fCommitting) {
|
|
#ifdef DEBUG
|
|
m_fCommitting = fCommitting;
|
|
#endif
|
|
}
|
|
|
|
private:
|
|
|
|
// GetNodeIdFromOffset() defined as a macro in the source
|
|
|
|
// Method to load a block from the stream if required
|
|
/*
|
|
HRESULT ConnectLeftSibling(
|
|
LPBLOCK_HEAP_NODE pNode,
|
|
LPBLOCK_HEAP_NODE pParent,
|
|
DWORD dwChildId
|
|
);
|
|
|
|
HRESULT ConnectRightSibling(
|
|
LPBLOCK_HEAP_NODE pNode,
|
|
LPBLOCK_HEAP_NODE pParent,
|
|
DWORD dwChildId
|
|
);
|
|
*/
|
|
|
|
HRESULT GetStream(
|
|
IMailMsgPropertyStream **ppStream,
|
|
BOOL fLockAcquired
|
|
);
|
|
|
|
HRESULT MoveToNode(
|
|
LPBLOCK_HEAP_NODE *ppNode,
|
|
HEAP_NODE_ID idTargetNode,
|
|
BOOL fLockAcquired
|
|
);
|
|
|
|
HRESULT GetNextNode(
|
|
LPBLOCK_HEAP_NODE *ppNode,
|
|
BOOL fLockAcquired
|
|
);
|
|
|
|
HRESULT LoadBlockIfUnavailable(
|
|
HEAP_NODE_ID idNode,
|
|
LPBLOCK_HEAP_NODE pParent,
|
|
HEAP_NODE_ID idChildNode,
|
|
LPBLOCK_HEAP_NODE *ppNode,
|
|
BOOL fLockAcquired
|
|
);
|
|
|
|
HRESULT GetEdgeListFromNodeId(
|
|
HEAP_NODE_ID idNode,
|
|
HEAP_NODE_ID *rgEdgeList,
|
|
DWORD *pdwEdgeCount
|
|
);
|
|
|
|
HRESULT GetNodeFromNodeId(
|
|
HEAP_NODE_ID idNode,
|
|
LPBLOCK_HEAP_NODE *ppNode,
|
|
BOOL fLockAcquired = FALSE
|
|
);
|
|
|
|
HRESULT GetParentNodeFromNodeId(
|
|
HEAP_NODE_ID idNode,
|
|
LPBLOCK_HEAP_NODE *ppNode
|
|
);
|
|
|
|
HRESULT GetPointerFromOffset(
|
|
FLAT_ADDRESS faOffset,
|
|
LPBYTE *ppbPointer,
|
|
DWORD *pdwRemainingSize,
|
|
LPBLOCK_HEAP_NODE *ppNode
|
|
);
|
|
|
|
HRESULT InsertNodeGivenPreviousNode(
|
|
LPBLOCK_HEAP_NODE pNodeToInsert,
|
|
LPBLOCK_HEAP_NODE pPreviousNode
|
|
);
|
|
|
|
BOOL IsMemoryAllocated(
|
|
FLAT_ADDRESS faOffset,
|
|
DWORD dwLength
|
|
);
|
|
|
|
HRESULT AllocateMemoryEx(
|
|
BOOL fAcquireLock,
|
|
DWORD dwSizeDesired,
|
|
FLAT_ADDRESS *pfaOffsetToAllocatedMemory,
|
|
DWORD *pdwSizeAllocated,
|
|
CBlockContext *pContext // Optional
|
|
);
|
|
|
|
HRESULT WriteAndIncrement(
|
|
LPBYTE pbBuffer,
|
|
FLAT_ADDRESS faOffset,
|
|
DWORD dwBytesToWrite,
|
|
DWORD *pdwValueToIncrement,
|
|
DWORD dwIncrementValue,
|
|
CBlockContext *pContext // Optional
|
|
);
|
|
|
|
HRESULT OperateOnMemory(
|
|
DWORD dwOperation,
|
|
LPBYTE pbBuffer,
|
|
FLAT_ADDRESS faTargetOffset,
|
|
DWORD dwBytesToDo,
|
|
DWORD *pdwBytesDone,
|
|
CBlockContext *pContext // Optional
|
|
);
|
|
|
|
HRESULT ReleaseNode(
|
|
LPBLOCK_HEAP_NODE pNode
|
|
);
|
|
|
|
DWORD m_dwSignature;
|
|
|
|
// This value indicates the current end of data. This is
|
|
// always changed with interlocked operations such that
|
|
// multiple threads can increment this variable and the
|
|
// increments are properly serialized
|
|
FLAT_ADDRESS m_faEndOfData;
|
|
|
|
HEAP_NODE_ID m_idNodeCount;
|
|
LPBLOCK_HEAP_NODE m_pRootNode;
|
|
|
|
CBlockManagerGetStream *m_pParent;
|
|
|
|
CBlockMemoryAccess m_bma;
|
|
|
|
#ifndef BLOCKMGR_DISABLE_CONTENTION_CONTROL
|
|
CShareLockNH m_rwLock;
|
|
#endif
|
|
|
|
IMailMsgProperties *m_pMsg;
|
|
|
|
BOOL m_fDirty;
|
|
#ifdef DEBUG
|
|
BOOL m_fCommitting;
|
|
#endif
|
|
|
|
};
|
|
|
|
|
|
#endif
|
|
|