MergeDateLogHandle

Merge a date range into an existing date log with conflict resolution

Overview

MergeDateLogHandle merges a source date range into a destination date log, with conflict resolution selected by flags. It is a high-level wrapper that manages synchronization, file I/O and recovery for date log merge operations.

Purpose

The function lets you consolidate multiple date logs or date ranges into a single authoritative date log. Two merge strategies are supported, selected by exactly one flag:

Key Features

Function Signature


HRESULT _Must_inspect_result_ _Success_(SUCCEEDED(return)) MergeDateLogHandle(
    _Inout_ _Pre_valid_            DATE_LOG_HANDLE   hDestDateLog,
    _In_ __readonly _Pre_valid_    DATE_RANGE_HANDLE hSourceDateLog,
    _In_                           DWORD             dwFlags
);

This declaration matches datelog.h.

Calling Convention

Standard C calling convention with SAL annotations for parameter validation and return value inspection.

Parameters

hDestDateLog

[In, Out] DATE_LOG_HANDLE

Handle to the destination date log that is modified with the merged data.

  • Direction: in/out; the date log’s contents change on success.
  • Valid values: a handle from InitializeDateLogHandle (HandleIsForDatelog) with a valid context and an associated holidays handle.
  • NULL: not allowed (pre-validated); an invalid handle yields ERR_INVALID_DATELOG_HANDLE.
  • Ownership / lifetime: remains owned by the caller and stays open; close it with CloseDateLogHandle. If the merge fails with ERR_DATE_LOG_RESTORE_FAILED the destination context may be invalid.
hSourceDateLog

[In] DATE_RANGE_HANDLE

Read-only handle to the source date range that is merged into the destination.

  • Direction: input only; the source is not modified during the operation.
  • Valid values: a date range (HandleIsForRange) whose context holds populated date items. It can represent a subset or a complete date range.
  • NULL: not allowed (pre-validated); an invalid handle yields ERR_INVALID_DATELOG_HANDLE.
  • Ownership / lifetime: remains owned by the caller; it is not consumed or closed by the merge.
dwFlags

[In] DWORD

Flags that control merge behavior. Exactly one conflict-resolution flag is required.

FlagValue and meaning
DATE_RANGE_MERGE_FAVOR_DEST0x04 — Keep destination GUIDs in the overlapping date range. Useful when the destination is the authoritative source.
DATE_RANGE_MERGE_FAVOR_SOURCE0x08 — Replace destination GUIDs with the source’s values in the overlapping range. Useful when the source has more recent or preferred data.
DATE_RANGE_MERGE_RESTORE_ON_FAILURE0x10 — (Optional) Enable automatic recovery if the merge fails. Requires destination file info to be present; creates a backup of the original array and file regions before modifying and restores the original state on failure.
  • Exactly one of DATE_RANGE_MERGE_FAVOR_DEST or DATE_RANGE_MERGE_FAVOR_SOURCE must be set.
  • If neither is present, the function returns ERROR_INVALID_FLAGS.
  • DATE_RANGE_MERGE_RESTORE_ON_FAILURE is optional and can be combined (OR) with either conflict-resolution flag.

Return Values

ValueMeaning
S_OKThe merge completed successfully; the destination date log has been updated.
ERR_INVALID_DATELOG_HANDLEThe destination or source handle is invalid, is not a date log/range, or its context is NULL.
ERROR_INVALID_FLAGSNeither DATE_RANGE_MERGE_FAVOR_DEST nor DATE_RANGE_MERGE_FAVOR_SOURCE was specified.
ERR_DATE_LOG_PENDING_IOThe required mutex locks could not be acquired within the 30-second timeout; another operation holds a lock or the system is under contention. Wait briefly and retry.
HRESULT_FROM_WIN32(GetLastError())Generic synchronization error from a mutex or lock acquisition failure.
ERROR_OUTOFMEMORYMemory for the merge context or backup structures could not be allocated.
ERROR_INVALID_HANDLEThe merge lock context allocation failed or an internal handle validation failed.
ERROR_FILE_CORRUPT, ERR_DATELOG_ZERO_GAP, ERR_DATEITEM_NOT_FOUND, ERR_DATE_EARLIER_BASE, ERR_DATE_IS_FUTURE_DATE, ERR_MAX_DATELOG_REACHEDReturned from the internal MergeDateLogCores() step.
ERR_DATE_LOG_RESTORE_FAILEDThe array or collection could not be restored after a merge failure; the destination context may be invalid and the handle should be closed.
ERR_DATE_FILE_RESTORE_FAILEDThe file region could not be restored from backup after a failed merge; verify the file’s integrity.

Test with SUCCEEDED/FAILED; the result must be inspected. The ERR_* codes are defined in date_error.h, which also documents ERR_DATELOG_MERGE_GAP (ranges neither overlap nor are contiguous), ERR_NOMERGE_BYFLAG, ERR_MERGE_NO_COMPLETE and INFO_MERGE_IS_COMPLETE for merge-related conditions. ERROR_INVALID_FLAGS, ERROR_OUTOFMEMORY, ERROR_INVALID_HANDLE and ERROR_FILE_CORRUPT are Win32 error codes, not library codes.

Behavior and Operations

Execution Flow

Validate Handles→Acquire Locks→Merge Cores→Update File→Release Locks

Lock Acquisition Strategy

Locks are acquired in a fixed order and released in reverse order, which prevents circular waits between threads:

  1. Destination range mutex (30 s timeout)
  2. Source range mutex (30 s timeout)
  3. Destination context mutex (30 s timeout)
  4. Source context mutex (30 s timeout)

Merge Process

  1. Allocate the MERGE_LOCK_CONTEXT that tracks merge state.
  2. Invoke MergeDateLogCores() to perform the in-memory merge.
  3. Mark the destination context as changed (bHasChanged = TRUE; see DateLogHandleHasChanged).
  4. If file information is present: duplicate the file handle and lock the affected regions.
  5. Update the destination file with the merged data via MergeDateLogFileInfo().
  6. Unlock the file regions and clean up resources.

File I/O Handling

If the destination has associated file info, the file handle is duplicated for safe concurrent I/O, the affected regions are locked, the merged items are written at the appropriate offsets, the number of written bytes is verified, and the regions are always unlocked before the locks are released.

Recovery on Failure

If DATE_RANGE_MERGE_RESTORE_ON_FAILURE is set and the merge or file update fails, the function checks whether the array backup was created, restores the in-memory array, restores the file regions from backup if the file was modified, and marks the context invalid if restoration itself fails.

Resource Cleanup

In the __finally block all acquired locks are released and temporary merge resources (backups, duplicated file handles, merge lock context) are freed.

Remarks

Thread Safety

The merge is thread-safe: both handles’ mutexes are acquired with a 30-second timeout using a consistent order, so two threads merging the same pair of handles in opposite directions cannot deadlock. If a lock cannot be acquired in time, ERR_DATE_LOG_PENDING_IO is returned and the operation can be retried. Other operations on the same context (including CloseDateLogHandle, which refuses to close a handle that is in use) are serialized against the merge.

Handle Lifetime and Ownership

Ordering and Related Functions

Note: The combined size of the destination after the merge cannot exceed DATE_LOG_MAX (0x591C8 = 370,120) items; otherwise ERR_MAX_DATELOG_REACHED is returned.

See Also

Sample Code

The sample builds a destination date log for 2025-01-02 through 2025-02-28 and a source date range for 2025-02-03 through 2025-03-31, merges the range into the log favoring the source’s GUIDs, retries a few times if the log is busy (ERR_DATE_LOG_PENDING_IO), and reports the item counts before and after. Both collections and the holidays handle are closed exactly once on every path.


#include <Windows.h>
#include <stdio.h>
#include "datelog.h"

static BOOL MakeFileTime(WORD wYear, WORD wMonth, WORD wDay, PFILETIME pFt)
{
    SYSTEMTIME st = { 0 };

    st.wYear = wYear;
    st.wMonth = wMonth;
    st.wDay = wDay;
    return SystemTimeToFileTime(&st, pFt);
}

/* Creates a holidays handle and loads it with the range [first, last]. */
static HRESULT BuildHolidays(PFILETIME pFirst, PFILETIME pLast, HOLIDAYS_HANDLE *phHolidays)
{
    HRESULT hr = S_OK;
    HOLIDAYS_HANDLE hHolidays = NULL;
    DATE_ITEM_HANDLE hFirst = NULL;
    DATE_ITEM_HANDLE hLast = NULL;

    *phHolidays = NULL;

    hr = InitializeDateLogHandle(NULL, DATE_LOG_MAX, NULL, &hHolidays, HandleIsForHolidays);
    if (FAILED(hr)) goto cleanup;

    hr = InitializeDateItem(hHolidays, pFirst, NULL, &hFirst);
    if (FAILED(hr)) goto cleanup;

    hr = InitializeDateItem(hHolidays, pLast, NULL, &hLast);
    if (FAILED(hr)) goto cleanup;

    hr = LoadDateLogHandle(NULL, hFirst, hLast, hHolidays, NULL, 0);
    if (FAILED(hr)) goto cleanup;

    *phHolidays = hHolidays;
    hHolidays = NULL;

cleanup:
    FreeDateItem(hLast);
    FreeDateItem(hFirst);
    if (hHolidays != NULL) {
        (void)CloseDateLogHandle(hHolidays);
    }
    return hr;
}

/* Creates a date log or range handle of the given type and generates [first, last] into it. */
static HRESULT BuildCollection(HOLIDAYS_HANDLE hHolidays, DateHandleType type,
                               PFILETIME pFirst, PFILETIME pLast, DATE_LOG_HANDLE *phCollection)
{
    HRESULT hr = S_OK;
    DATE_LOG_HANDLE hCollection = NULL;
    DATE_ITEM_HANDLE hFirst = NULL;
    DATE_ITEM_HANDLE hLast = NULL;

    *phCollection = NULL;

    hr = InitializeDateLogHandle(NULL, DATE_LOG_MAX, hHolidays, &hCollection, type);
    if (FAILED(hr)) goto cleanup;

    hr = InitializeDateItem(hHolidays, pFirst, NULL, &hFirst);
    if (FAILED(hr)) goto cleanup;

    hr = InitializeDateItem(hHolidays, pLast, NULL, &hLast);
    if (FAILED(hr)) goto cleanup;

    hr = LoadDateLogHandle(NULL, hFirst, hLast, hCollection, NULL, 0);
    if (FAILED(hr)) goto cleanup;

    *phCollection = hCollection;
    hCollection = NULL;

cleanup:
    FreeDateItem(hLast);
    FreeDateItem(hFirst);
    if (hCollection != NULL) {
        (void)CloseDateLogHandle(hCollection);
    }
    return hr;
}

int main(void)
{
    int exitCode = 1;
    HRESULT hr = S_OK;
    FILETIME ftFirst = { 0 };
    FILETIME ftDestLast = { 0 };
    FILETIME ftSourceFirst = { 0 };
    FILETIME ftLast = { 0 };
    HOLIDAYS_HANDLE hHolidays = NULL;
    DATE_LOG_HANDLE hDest = NULL;
    DATE_RANGE_HANDLE hSource = NULL;
    DWORD dwBefore = 0;
    DWORD dwAfter = 0;
    int attempt = 0;

    if (!MakeFileTime(2025, 1, 2, &ftFirst) ||
        !MakeFileTime(2025, 2, 28, &ftDestLast) ||
        !MakeFileTime(2025, 2, 3, &ftSourceFirst) ||
        !MakeFileTime(2025, 3, 31, &ftLast)) {
        printf("Invalid calendar date.\n");
        return 1;
    }

    /* The holidays handle covers the whole span used by both collections. */
    hr = BuildHolidays(&ftFirst, &ftLast, &hHolidays);
    if (FAILED(hr)) {
        printf("BuildHolidays failed: 0x%08lX\n", (unsigned long)hr);
        goto cleanup;
    }

    /* Destination: 2025-01-02 .. 2025-02-28. Source range: 2025-02-03 .. 2025-03-31 (overlaps). */
    hr = BuildCollection(hHolidays, HandleIsForDatelog, &ftFirst, &ftDestLast, &hDest);
    if (FAILED(hr)) {
        printf("Building the destination failed: 0x%08lX\n", (unsigned long)hr);
        goto cleanup;
    }
    hr = BuildCollection(hHolidays, HandleIsForRange, &ftSourceFirst, &ftLast, &hSource);
    if (FAILED(hr)) {
        printf("Building the source range failed: 0x%08lX\n", (unsigned long)hr);
        goto cleanup;
    }

    dwBefore = GetDateCollectionLength(hDest);
    if (dwBefore > DATE_LOG_MAX) {
        printf("GetDateCollectionLength reported a failure.\n");
        goto cleanup;
    }

    /* Exactly one conflict-resolution flag is required. DATE_RANGE_MERGE_RESTORE_ON_FAILURE is
       optional and needs file information on the destination, so it is not used here. */
    for (attempt = 0; attempt < 3; attempt++) {
        hr = MergeDateLogHandle(hDest, hSource, DATE_RANGE_MERGE_FAVOR_SOURCE);
        if (hr != ERR_DATE_LOG_PENDING_IO) {
            break;
        }
        printf("The date log is busy; retrying...\n");
        Sleep(100);
    }

    if (FAILED(hr)) {
        if (hr == ERR_DATELOG_MERGE_GAP) {
            printf("The ranges neither overlap nor touch.\n");
        } else if (hr == ERR_DATE_LOG_RESTORE_FAILED) {
            printf("The merge failed and the destination could not be restored; recreate it.\n");
        } else {
            printf("MergeDateLogHandle failed: 0x%08lX\n", (unsigned long)hr);
        }
        goto cleanup;
    }

    dwAfter = GetDateCollectionLength(hDest);
    printf("Merged: the destination grew from %lu to %lu date items.\n",
           (unsigned long)dwBefore, (unsigned long)dwAfter);
    if (DateLogHandleHasChanged(hDest)) {
        printf("The destination has unsaved changes; write the file if one is attached.\n");
    }

    exitCode = 0;

cleanup:
    if (hSource != NULL) {
        (void)CloseDateLogHandle(hSource);
    }
    if (hDest != NULL) {
        (void)CloseDateLogHandle(hDest);
    }
    if (hHolidays != NULL) {
        (void)CloseDateLogHandle(hHolidays);
    }
    return exitCode;
}