Skip to content

Commit 65b16da

Browse files
committed
Check for race condition when delete before a write finishes
1 parent c79b9ca commit 65b16da

File tree

2 files changed

+33
-3
lines changed

2 files changed

+33
-3
lines changed

src/Persistence/DocumentStore.php

Lines changed: 28 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,13 @@
2020
*/
2121
final class DocumentStore
2222
{
23-
/** @var int[] PIDs of outstanding async child processes. */
23+
/**
24+
* PIDs of outstanding async child processes, keyed by nodeId.
25+
* Keying by nodeId lets waitForNode() drain exactly one write without
26+
* blocking every other in-flight write.
27+
*
28+
* @var array<int, int> nodeId → PID
29+
*/
2430
private array $pendingPids = [];
2531

2632
public function __construct(private readonly string $docsDir) {}
@@ -59,8 +65,8 @@ public function write(
5965
$this->writeSync($nodeId, $docId, $text, $metadata);
6066
exit(0);
6167
} else {
62-
// Parent: record PID and return.
63-
$this->pendingPids[] = $pid;
68+
// Parent: record PID keyed by nodeId and return.
69+
$this->pendingPids[$nodeId] = $pid;
6470
return;
6571
}
6672
}
@@ -69,6 +75,25 @@ public function write(
6975
$this->writeSync($nodeId, $docId, $text, $metadata);
7076
}
7177

78+
/**
79+
* Block until the async write for a specific node has completed.
80+
*
81+
* Use this before deleting a node's file so a late child write cannot
82+
* recreate {nodeId}.bin after the unlink().
83+
*/
84+
public function waitForNode(int $nodeId): void
85+
{
86+
if (!isset($this->pendingPids[$nodeId])) {
87+
return;
88+
}
89+
90+
if (function_exists('pcntl_waitpid')) {
91+
pcntl_waitpid($this->pendingPids[$nodeId], $status);
92+
}
93+
94+
unset($this->pendingPids[$nodeId]);
95+
}
96+
7297
/**
7398
* Block until every outstanding async write has completed.
7499
* Must be called before index files are written (see VectorDatabase::save()).

src/VectorDatabase.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -204,6 +204,11 @@ public function deleteDocument(string|int $id): bool
204204

205205
// Delete document file from disk if persistence is enabled.
206206
if ($this->path !== null) {
207+
// An async pcntl_fork child may still be writing {nodeId}.bin.
208+
// Wait for that specific write to finish before unlinking so the
209+
// child cannot recreate the file after we delete it.
210+
$this->getDocumentStore()->waitForNode($nodeId);
211+
207212
$docFile = $this->path . '/docs/' . $nodeId . '.bin';
208213
if (file_exists($docFile)) {
209214
// Suppress PHP warning and handle failure explicitly to keep

0 commit comments

Comments
 (0)