mikser-io 11.10.0 → 11.10.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/journal.js +46 -1
package/package.json
CHANGED
package/src/journal.js
CHANGED
|
@@ -235,6 +235,25 @@ export async function* useJournal(name, operations, signal) {
|
|
|
235
235
|
|
|
236
236
|
// Yield returned — caller's for-body completed for this
|
|
237
237
|
// iteration. Diff and write back if mutated.
|
|
238
|
+
//
|
|
239
|
+
// WHICH MEANS: a consumer that mutates `entry.entity` MUST finish
|
|
240
|
+
// that mutation before its loop body completes. `for await` does
|
|
241
|
+
// this by construction. Mapping over this generator concurrently
|
|
242
|
+
// does NOT — p-map and friends pull the next item as soon as a
|
|
243
|
+
// worker slot frees, which resumes the generator here, before any
|
|
244
|
+
// body has finished. The diff then sees an untouched entity and
|
|
245
|
+
// writes nothing.
|
|
246
|
+
//
|
|
247
|
+
// Not a race that usually works: measured at concurrency 2 and 4,
|
|
248
|
+
// zero of six mutations survived. Every one, silently, with
|
|
249
|
+
// whatever the work cost already spent.
|
|
250
|
+
//
|
|
251
|
+
// Concurrent mapping over the journal is still fine for a plugin
|
|
252
|
+
// that writes somewhere else — mikser-io-vector embeds into an
|
|
253
|
+
// external store and never touches `entity`. A plugin that
|
|
254
|
+
// mutates entities and wants concurrency has to gather first, do
|
|
255
|
+
// the work off the journal, then apply inside a second walk;
|
|
256
|
+
// mikser-io-ocr does exactly that and says why.
|
|
238
257
|
if (entry.entity != null) {
|
|
239
258
|
const currentEntity = JSON.stringify(entry.entity)
|
|
240
259
|
if (currentEntity !== originalEntity) {
|
|
@@ -317,5 +336,31 @@ onFinalized(async () => {
|
|
|
317
336
|
})
|
|
318
337
|
|
|
319
338
|
onCancelled(async () => {
|
|
320
|
-
|
|
339
|
+
// NOT cleared. A cancelled cycle's entries are not superseded work —
|
|
340
|
+
// "this entity was created and nothing has processed it" is still true
|
|
341
|
+
// after the restart, and the restarted cycle will not re-journal it: the
|
|
342
|
+
// source gate compares checksums, finds the file unchanged, and emits
|
|
343
|
+
// nothing. So deleting these rows orphaned the entity permanently. It sat
|
|
344
|
+
// in the catalog with empty meta, no output, and no journal entry any
|
|
345
|
+
// future cycle would look at, under a green "Mikser completed" — and
|
|
346
|
+
// touching the file was the only way to get it back.
|
|
347
|
+
//
|
|
348
|
+
// Reported against a 4 MB PDF uploaded over WebDAV: the upload landed
|
|
349
|
+
// mid-cycle, the restart deleted the CREATE entry, and the page was never
|
|
350
|
+
// produced. Nothing about it was specific to the plugin that noticed —
|
|
351
|
+
// any consumer doing real work between a journal read and its write-back
|
|
352
|
+
// had the same hole.
|
|
353
|
+
//
|
|
354
|
+
// The asymmetry was the tell: a CRASH leaves these rows and `--resume`
|
|
355
|
+
// continues from them (see the leftover bootstrap above), while a restart
|
|
356
|
+
// deliberately deleted them. Keeping them makes a restart behave like the
|
|
357
|
+
// case the engine already handles.
|
|
358
|
+
//
|
|
359
|
+
// They are cleared by onFinalized when a cycle actually completes, so a
|
|
360
|
+
// run of cancellations carries its backlog forward and the first cycle to
|
|
361
|
+
// finish clears the lot.
|
|
362
|
+
const carried = stmtCount.get().n
|
|
363
|
+
if (carried > 0) {
|
|
364
|
+
useLogger()?.debug('Restart: carrying %d unprocessed journal entries into the next cycle', carried)
|
|
365
|
+
}
|
|
321
366
|
})
|