Skip to content

[BUG]: PGlite suppresses synchronization while inside a transaction #1065

Description

@AntonOfTheWoods

Describe the bug

Summary

When relaxedDurability: false, successful top-level query() and exec() calls await syncToFs() before returning. transaction() does not provide the equivalent synchronization boundary.

Inside a transaction, #runQuery() and #runExec() correctly suppress synchronization while #inTransaction is true. However, transaction() executes COMMIT while that flag is still true, then clears the flag and returns without calling syncToFs().

To Reproduce - include code sample(s)

As a result, this can resolve without synchronizing the filesystem:

await pg.transaction(async (tx) => {
  await tx.exec("INSERT INTO example ...");
});

// No syncToFs() has occurred after COMMIT.

Details

  • PGlite version: 0.5.4

Additional context

BEGIN currently causes a sync before #inTransaction is set, but there is no sync after the transaction’s filesystem changes.

Impact

This is significant for custom filesystem implementations where syncToFs() is the awaited durability and health boundary.

Even with relaxedDurability: false:

  • await pg.transaction(...) can resolve before the committed transaction is persisted by the filesystem.
  • Worker, tab, or process termination immediately afterwards can lose work that callers reasonably believed had crossed the same boundary as a successful query() or exec().
  • Filesystem synchronization failures or latched health failures are not delivered until a later top-level operation calls syncToFs().

This makes transaction completion behave differently from other successful PGlite operations.

Relevant control flow

#runQuery() and #runExec() contain:

if (!this.#inTransaction) {
  await this.syncToFs();
}

But the successful transaction path is effectively:

await this.#runExec("COMMIT"); // #inTransaction is still true
this.#inTransaction = false;
return result; // No syncToFs()

Explicit rollback and exception-driven rollback have the same missing terminal synchronization boundary.

Expected behavior

Before transaction() resolves, its terminal path should perform the normal awaited filesystem synchronization after leaving the in-transaction state.

Ideally, this should happen while the transaction exclusivity boundary is still held, so another operation cannot interleave between the transaction ending and its filesystem sync.

The relevant paths are:

  • successful COMMIT;
  • explicit tx.rollback();
  • exception-driven ROLLBACK.

Error precedence on the rollback/exception path may need an explicit decision, but the filesystem should at least receive the terminal sync or health check before transaction exclusivity is released.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions