@rotorsoft/act 1.32.5 → 1.32.6

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/dist/index.js CHANGED
@@ -1070,6 +1070,23 @@ async function run_close_cycle(targets, deps) {
1070
1070
  const skipped = [];
1071
1071
  const windowed_result = windowed.length ? await run_windowed_closes(windowed, deps, skipped) : /* @__PURE__ */ new Map();
1072
1072
  if (!full.length) return { truncated: windowed_result, skipped };
1073
+ const truncated = await with_stream_locks(
1074
+ full.map((t) => t.stream),
1075
+ deps,
1076
+ () => run_full_closes(full, target_map, deps, skipped)
1077
+ );
1078
+ for (const [stream, entry] of windowed_result) truncated.set(stream, entry);
1079
+ return { truncated, skipped };
1080
+ }
1081
+ function with_stream_locks(streams, deps, work) {
1082
+ const with_lock = deps.with_stream_lock ?? ((_stream, w) => w());
1083
+ return [...streams].sort().reduceRight(
1084
+ (inner, stream) => () => with_lock(stream, inner),
1085
+ work
1086
+ )();
1087
+ }
1088
+ async function run_full_closes(full, target_map, deps, skipped) {
1089
+ const none = /* @__PURE__ */ new Map();
1073
1090
  const streams = full.map((t) => t.stream);
1074
1091
  const stream_info = await scan_stream_heads(streams);
1075
1092
  for (const target of full) {
@@ -1092,7 +1109,7 @@ async function run_close_cycle(targets, deps) {
1092
1109
  deps.probe_page_size ?? SAFETY_PROBE_PAGE_SIZE,
1093
1110
  deps.catch_up_correlation
1094
1111
  );
1095
- if (!safe.length) return { truncated: windowed_result, skipped };
1112
+ if (!safe.length) return none;
1096
1113
  const { guarded, guard_events } = await guard_with_tombstones(
1097
1114
  safe,
1098
1115
  stream_info,
@@ -1100,7 +1117,7 @@ async function run_close_cycle(targets, deps) {
1100
1117
  deps.tombstone,
1101
1118
  skipped
1102
1119
  );
1103
- if (!guarded.length) return { truncated: windowed_result, skipped };
1120
+ if (!guarded.length) return none;
1104
1121
  const seed_states = await load_restart_seeds(
1105
1122
  guarded,
1106
1123
  target_map,
@@ -1110,14 +1127,18 @@ async function run_close_cycle(targets, deps) {
1110
1127
  deps.logger
1111
1128
  );
1112
1129
  await run_archive_callbacks(guarded, target_map);
1113
- const truncated = await truncate_and_warm_cache(
1130
+ const still_guarded = await heads_still_guarded(
1114
1131
  guarded,
1132
+ guard_events,
1133
+ skipped
1134
+ );
1135
+ if (!still_guarded.length) return none;
1136
+ return truncate_and_warm_cache(
1137
+ still_guarded,
1115
1138
  seed_states,
1116
1139
  guard_events,
1117
1140
  deps.correlation
1118
1141
  );
1119
- for (const [stream, entry] of windowed_result) truncated.set(stream, entry);
1120
- return { truncated, skipped };
1121
1142
  }
1122
1143
  async function run_windowed_closes(windowed, deps, skipped) {
1123
1144
  const checkpoint = deps.reactive_events_size > 0 ? await deps.catch_up_correlation(Number.MAX_SAFE_INTEGER) : -1;
@@ -1307,6 +1328,15 @@ async function run_archive_callbacks(guarded, target_map) {
1307
1328
  if (archive_fn) await archive_fn();
1308
1329
  }
1309
1330
  }
1331
+ async function heads_still_guarded(guarded, guard_events, skipped) {
1332
+ const heads = await store().query_stats(guarded, {});
1333
+ return guarded.filter((stream) => {
1334
+ if (heads.get(stream)?.head.id === guard_events.get(stream).id)
1335
+ return true;
1336
+ skipped.push(stream);
1337
+ return false;
1338
+ });
1339
+ }
1310
1340
  async function truncate_and_warm_cache(guarded, seed_states, guard_events, correlation) {
1311
1341
  const trunc_targets = guarded.map((stream) => {
1312
1342
  const snapshot = seed_states.get(stream);
@@ -3581,9 +3611,9 @@ var Act = class {
3581
3611
  */
3582
3612
  _close_locks = /* @__PURE__ */ new Map();
3583
3613
  /**
3584
- * Run `work` under the per-stream close lock (#1222). Serializes
3585
- * windowed-close critical sections for the same stream while letting
3586
- * different streams proceed in parallel.
3614
+ * Run `work` under the per-stream close lock (#1222). Serializes close
3615
+ * critical sections for the same stream (windowed prunes, and full closes
3616
+ * since #1738) while letting different streams proceed in parallel.
3587
3617
  */
3588
3618
  _with_close_lock(stream, work) {
3589
3619
  const prev = this._close_locks.get(stream) ?? Promise.resolve();