fix(local_store): DocStore.lock() no-op on Windows causes race condition and duplicate document names · Issue #561 · VectifyAI/PageIndex · GitHub
Skip to content

fix(local_store): DocStore.lock() no-op on Windows causes race condition and duplicate document names #561

Description

@shravan606756

Title

[Bug] Windows concurrency defect in DocStore.lock() causes duplicate document names during concurrent submissions

Description

In pageindex/local_store.py, DocStore.lock() uses fcntl.flock() for file locking:

try:
    import fcntl
except ImportError:
    yield
    return

On Windows, fcntl is unavailable in the Python standard library. When importing it raises ImportError, the context manager silently yields without acquiring a lock. The current implementation also does not provide an in-process thread lock as a fallback.

As a result, concurrent calls to LocalAPI.submit_document() on Windows can execute the document-name check-and-save sequence (_unique_doc_name() followed by _store.save_document()) without mutual exclusion.

Two callers may both observe that the same filename is available (for example, sample.pdf) and save documents under that name, violating the intended unique-name invariant.

Reproduction

Run the following test on Windows:

python -m pytest tests/test_client.py::test_concurrent_same_name_submits_store_unique_names -q

The test consistently fails with:

AssertionError: assert {'sample.pdf'} == {'sample.pdf', 'sample_1.pdf'}

The test expects concurrent submissions of the same file to receive distinct stored names.

CI Consideration

The current .github/workflows/tests.yml workflow uses ubuntu-latest. Since the locking implementation takes a different path on Windows, adding Windows CI coverage would help detect platform-specific concurrency regressions.

Requested behavior and acceptance criteria

  • Concurrent submissions of documents with the same filename must receive distinct stored names. Neither submission should silently overwrite or shadow the other because of a naming collision.

  • The filename uniqueness check and document save operation must execute within the same protected critical section, preventing concurrent callers from selecting the same available name.

  • Thread safety must be guaranteed when multiple threads submit documents through the same DocStore.

  • Separate DocStore instances targeting the same storage directory must coordinate correctly, rather than relying only on instance-local synchronization.

  • Cross-process locking must continue to work on POSIX systems using fcntl.flock().

  • Windows must provide equivalent cross-process locking guarantees through an appropriate platform-specific mechanism, such as msvcrt.locking(), rather than silently proceeding without a lock.

  • Nested lock acquisition and release must behave safely, without deadlocks, premature lock release, or platform-specific locking errors.

  • Deterministic tests must cover concurrent same-name submissions and relevant thread-level and cross-process locking behavior, including Windows-specific behavior where applicable.

  • Existing storage semantics, document identifiers, and document submission behavior must remain backward compatible.

Proposed direction

Implement cross-platform synchronization that combines in-process thread safety with operating-system-level file locking. Ensure that lock-file creation, acquisition, release, and reentrancy are handled correctly.

The implementation should preserve existing POSIX behavior while providing equivalent concurrency guarantees on Windows. The exact internal design can be determined during implementation and review.

Activity

  1. Varshith-Kali commented on Oct 17, 2026

    @Varshith-Kali

    I'd like to take this one.

    The bug is real: on Windows import fcntl raises ImportError and DocStore.lock() silently yields with no lock, so concurrent submit_document calls can race the check-and-save in _unique_doc_name + _store.save_document and produce duplicate names.

    My plan:

    1. Add a cross-platform exclusive lock: fcntl.flock on POSIX (unchanged), msvcrt.locking on Windows for the cross-process case, plus an in-process threading.Lock so threads within one process also serialize.
    2. Keep the unique-name check and the document save inside the same critical section so the invariant holds under concurrency.
    3. Add a regression test simulating the Windows path (no fcntl) with concurrent same-name submissions asserting distinct stored names.
    4. Look into adding a Windows runner to the tests workflow as follow-up.

    I'll open a PR with the fix and tests.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions