windows-nt/Source/XPSP1/NT/inetsrv/iis/staxinc/export/blockmgr.h

1052 lines
35 KiB
C
Raw Permalink Normal View History

2020-09-26 03:20:57 -05:00
/*++
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