@bian-womp/spark-graph 0.3.73 → 0.3.74

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/lib/cjs/index.cjs CHANGED
@@ -1684,6 +1684,7 @@ class RunContextManager {
1684
1684
  id,
1685
1685
  startNodes: new Set([startNodeId]),
1686
1686
  cancelledNodes: new Set(),
1687
+ pendingScheduling: 1,
1687
1688
  pendingNodes: 0,
1688
1689
  pendingEdges: 0,
1689
1690
  pendingResolvers: 0,
@@ -1719,6 +1720,33 @@ class RunContextManager {
1719
1720
  hasActiveRunContexts() {
1720
1721
  return this.runContexts.size > 0;
1721
1722
  }
1723
+ /**
1724
+ * Release one scheduling hold from a run-context.
1725
+ * Must be called once by the creator when scheduling decisions are complete.
1726
+ */
1727
+ releaseScheduling(id) {
1728
+ const ctx = this.runContexts.get(id);
1729
+ if (!ctx) {
1730
+ this.logger.debug("release-scheduling-context-not-found", {
1731
+ runContextId: id,
1732
+ });
1733
+ return;
1734
+ }
1735
+ if (ctx.pendingScheduling > 0) {
1736
+ ctx.pendingScheduling--;
1737
+ }
1738
+ else {
1739
+ this.logger.warn("release-scheduling-underflow", {
1740
+ runContextId: id,
1741
+ pendingScheduling: ctx.pendingScheduling,
1742
+ });
1743
+ }
1744
+ this.logger.debug("release-scheduling", {
1745
+ runContextId: id,
1746
+ pendingScheduling: ctx.pendingScheduling,
1747
+ });
1748
+ this.finishRunContextIfPossible(id);
1749
+ }
1722
1750
  /**
1723
1751
  * Increment queued work count for a run-context.
1724
1752
  * Used by asyncConcurrency: "queue" to keep contexts alive while work is queued.
@@ -1870,7 +1898,11 @@ class RunContextManager {
1870
1898
  });
1871
1899
  return;
1872
1900
  }
1873
- if (ctx.pendingNodes > 0 || ctx.pendingEdges > 0 || ctx.pendingResolvers > 0 || ctx.pendingQueued > 0) {
1901
+ if (ctx.pendingScheduling > 0 ||
1902
+ ctx.pendingNodes > 0 ||
1903
+ ctx.pendingEdges > 0 ||
1904
+ ctx.pendingResolvers > 0 ||
1905
+ ctx.pendingQueued > 0) {
1874
1906
  return; // Still has pending work
1875
1907
  }
1876
1908
  this.logger.info("finish-run-context", {
@@ -2980,13 +3012,22 @@ class NodeExecutor {
2980
3012
  execute: (opts) => {
2981
3013
  if (this.graph.allInboundHaveValue(nodeId)) {
2982
3014
  let runContextIdsToUse = this.runtime.getRunMode() === "auto" ? undefined : runContextIds;
3015
+ let runContextIdToRelease;
2983
3016
  if (this.runtime.getRunMode() === "manual" && (!runContextIds || runContextIds.size === 0)) {
2984
- runContextIdsToUse = new Set([this.runContextManager.createRunContext(nodeId, opts)]);
3017
+ runContextIdToRelease = this.runContextManager.createRunContext(nodeId, opts);
3018
+ runContextIdsToUse = new Set([runContextIdToRelease]);
3019
+ }
3020
+ try {
3021
+ this.execute(nodeId, {
3022
+ runContextIds: runContextIdsToUse,
3023
+ reason: opts?.reason ?? "executeFromContext",
3024
+ });
3025
+ }
3026
+ finally {
3027
+ if (runContextIdToRelease) {
3028
+ this.runContextManager.releaseScheduling(runContextIdToRelease);
3029
+ }
2985
3030
  }
2986
- this.execute(nodeId, {
2987
- runContextIds: runContextIdsToUse,
2988
- reason: opts?.reason ?? "executeFromContext",
2989
- });
2990
3031
  }
2991
3032
  },
2992
3033
  getInput: (handle) => inputs[handle],
@@ -3012,6 +3053,7 @@ class NodeExecutor {
3012
3053
  */
3013
3054
  execute(nodeId, opts) {
3014
3055
  let { runContextIds, canSkipHandleResolution, reason = "" } = opts ?? {};
3056
+ let autoCreatedRunContextId;
3015
3057
  const node = this.graph.getNode(nodeId);
3016
3058
  if (!node)
3017
3059
  return;
@@ -3024,81 +3066,89 @@ class NodeExecutor {
3024
3066
  if (runMode === "manual" && (!runContextIds || runContextIds.size === 0)) {
3025
3067
  // If autoRun is true, auto-generate a run context (similar to createExecutionContext pattern)
3026
3068
  if (node.policy?.autoRun === true) {
3027
- runContextIds = new Set([this.runContextManager.createRunContext(nodeId, { propagate: false })]);
3069
+ autoCreatedRunContextId = this.runContextManager.createRunContext(nodeId, { propagate: false });
3070
+ runContextIds = new Set([autoCreatedRunContextId]);
3028
3071
  }
3029
3072
  else {
3030
3073
  console.trace(`NodeExecutor.execute[${formatNodeRef(this.graph, nodeId)}:${reason}]: no runContextIds provided in manual mode, skipping execution`);
3031
3074
  return;
3032
3075
  }
3033
3076
  }
3034
- if (runMode === "auto" && runContextIds && runContextIds.size > 0) {
3035
- console.trace(`NodeExecutor.execute[${formatNodeRef(this.graph, nodeId)}:${reason}]: runContextIds provided in auto mode, ignoring`);
3036
- runContextIds = undefined;
3037
- }
3038
- // Early validation for auto-mode paused state
3039
- if (this.runtime.isPaused())
3040
- return;
3041
- // Check runtime validators (check current state, not just graph definition)
3042
- const runtimeValidationError = this.runtime.hasRuntimeValidationBlock(nodeId);
3043
- if (runtimeValidationError) {
3044
- this.eventEmitter.emit("error", {
3045
- kind: "system",
3046
- message: runtimeValidationError.message,
3047
- code: runtimeValidationError.code || "RUNTIME_VALIDATION_BLOCKED",
3048
- details: {
3049
- nodeId,
3050
- nodeTypeId: node?.typeId,
3051
- ...runtimeValidationError.details,
3052
- },
3053
- });
3054
- return;
3055
- }
3056
- // Attach run-context IDs if provided - do this BEFORE checking for pending resolution
3057
- // so that handle resolution can track these run contexts
3058
- if (runContextIds) {
3059
- this.graph.addNodeRunContextIds(nodeId, runContextIds);
3060
- }
3061
- if (!canSkipHandleResolution && !this.handleResolver.getPendingResolution(nodeId)) {
3062
- this.handleResolver.scheduleRecomputeHandles(nodeId);
3063
- }
3064
- // Check if handles are being resolved - wait for resolution before executing
3065
- // Do this AFTER setting up run contexts so handle resolution can track them
3066
- const pendingResolution = this.handleResolver.getPendingResolution(nodeId);
3067
- if (pendingResolution) {
3068
- if (runContextIds && runContextIds.size > 0) {
3069
- for (const id of runContextIds) {
3070
- this.runContextManager.startHandleResolution(id, nodeId);
3071
- }
3077
+ try {
3078
+ if (runMode === "auto" && runContextIds && runContextIds.size > 0) {
3079
+ console.trace(`NodeExecutor.execute[${formatNodeRef(this.graph, nodeId)}:${reason}]: runContextIds provided in auto mode, ignoring`);
3080
+ runContextIds = undefined;
3072
3081
  }
3073
- // Wait for resolution to complete, then re-execute
3074
- pendingResolution.then(() => {
3075
- // Re-check node still exists and conditions
3076
- const nodeAfter = this.graph.getNode(nodeId);
3077
- if (nodeAfter) {
3078
- this.execute(nodeId, {
3079
- runContextIds,
3080
- canSkipHandleResolution: true,
3081
- reason: opts?.reason,
3082
- });
3083
- }
3082
+ // Early validation for auto-mode paused state
3083
+ if (this.runtime.isPaused())
3084
+ return;
3085
+ // Check runtime validators (check current state, not just graph definition)
3086
+ const runtimeValidationError = this.runtime.hasRuntimeValidationBlock(nodeId);
3087
+ if (runtimeValidationError) {
3088
+ this.eventEmitter.emit("error", {
3089
+ kind: "system",
3090
+ message: runtimeValidationError.message,
3091
+ code: runtimeValidationError.code || "RUNTIME_VALIDATION_BLOCKED",
3092
+ details: {
3093
+ nodeId,
3094
+ nodeTypeId: node?.typeId,
3095
+ ...runtimeValidationError.details,
3096
+ },
3097
+ });
3098
+ return;
3099
+ }
3100
+ // Attach run-context IDs if provided - do this BEFORE checking for pending resolution
3101
+ // so that handle resolution can track these run contexts
3102
+ if (runContextIds) {
3103
+ this.graph.addNodeRunContextIds(nodeId, runContextIds);
3104
+ }
3105
+ if (!canSkipHandleResolution && !this.handleResolver.getPendingResolution(nodeId)) {
3106
+ this.handleResolver.scheduleRecomputeHandles(nodeId);
3107
+ }
3108
+ // Check if handles are being resolved - wait for resolution before executing
3109
+ // Do this AFTER setting up run contexts so handle resolution can track them
3110
+ const pendingResolution = this.handleResolver.getPendingResolution(nodeId);
3111
+ if (pendingResolution) {
3084
3112
  if (runContextIds && runContextIds.size > 0) {
3085
3113
  for (const id of runContextIds) {
3086
- this.runContextManager.finishHandleResolution(id, nodeId);
3114
+ this.runContextManager.startHandleResolution(id, nodeId);
3087
3115
  }
3088
3116
  }
3089
- });
3090
- return;
3117
+ // Wait for resolution to complete, then re-execute
3118
+ pendingResolution.then(() => {
3119
+ // Re-check node still exists and conditions
3120
+ const nodeAfter = this.graph.getNode(nodeId);
3121
+ if (nodeAfter) {
3122
+ this.execute(nodeId, {
3123
+ runContextIds,
3124
+ canSkipHandleResolution: true,
3125
+ reason: opts?.reason,
3126
+ });
3127
+ }
3128
+ if (runContextIds && runContextIds.size > 0) {
3129
+ for (const id of runContextIds) {
3130
+ this.runContextManager.finishHandleResolution(id, nodeId);
3131
+ }
3132
+ }
3133
+ });
3134
+ return;
3135
+ }
3136
+ // Handle debouncing
3137
+ const now = Date.now();
3138
+ if (this.shouldDebounce(nodeId, now)) {
3139
+ this.handleDebouncedSchedule(nodeId, now, runContextIds, reason);
3140
+ return;
3141
+ }
3142
+ // Prepare execution plan
3143
+ const executionPlan = this.prepareExecutionPlan(nodeId, runContextIds, now, reason);
3144
+ // Route to appropriate concurrency handler
3145
+ this.routeToConcurrencyHandler(nodeId, executionPlan);
3091
3146
  }
3092
- // Handle debouncing
3093
- const now = Date.now();
3094
- if (this.shouldDebounce(nodeId, now)) {
3095
- this.handleDebouncedSchedule(nodeId, now, runContextIds, reason);
3096
- return;
3147
+ finally {
3148
+ if (autoCreatedRunContextId) {
3149
+ this.runContextManager.releaseScheduling(autoCreatedRunContextId);
3150
+ }
3097
3151
  }
3098
- // Prepare execution plan
3099
- const executionPlan = this.prepareExecutionPlan(nodeId, runContextIds, now, reason);
3100
- // Route to appropriate concurrency handler
3101
- this.routeToConcurrencyHandler(nodeId, executionPlan);
3102
3152
  }
3103
3153
  /**
3104
3154
  * Check if execution should be debounced
@@ -3716,16 +3766,26 @@ class GraphRuntime {
3716
3766
  executeNodeAutoRun(nodeId, opts) {
3717
3767
  const node = this.graph.getNode(nodeId);
3718
3768
  const shouldAutoRun = this.runMode === "auto" || node?.policy?.autoRun === true;
3769
+ const canExecute = this.graph.allInboundHaveValue(nodeId);
3770
+ if (!shouldAutoRun || !canExecute)
3771
+ return;
3719
3772
  let runContextIdsToUse = undefined;
3773
+ let runContextIdToRelease;
3720
3774
  if (this.runMode === "manual") {
3721
- runContextIdsToUse = new Set([this.runContextManager.createRunContext(nodeId, { propagate: false })]);
3775
+ runContextIdToRelease = this.runContextManager.createRunContext(nodeId, { propagate: false });
3776
+ runContextIdsToUse = new Set([runContextIdToRelease]);
3722
3777
  }
3723
- if (shouldAutoRun && this.graph.allInboundHaveValue(nodeId)) {
3778
+ try {
3724
3779
  this.execute(nodeId, {
3725
3780
  runContextIds: runContextIdsToUse,
3726
3781
  reason: opts?.reason ?? "executeNodeAutoRun",
3727
3782
  });
3728
3783
  }
3784
+ finally {
3785
+ if (runContextIdToRelease) {
3786
+ this.runContextManager.releaseScheduling(runContextIdToRelease);
3787
+ }
3788
+ }
3729
3789
  }
3730
3790
  setInputs(nodeId, inputs) {
3731
3791
  const node = this.graph.getNode(nodeId);
@@ -3810,8 +3870,9 @@ class GraphRuntime {
3810
3870
  });
3811
3871
  if (this.runMode === "auto" && invalidate) {
3812
3872
  for (const nodeId of this.graph.getNodeIds()) {
3813
- if (this.graph.allInboundHaveValue(nodeId))
3873
+ if (this.graph.allInboundHaveValue(nodeId)) {
3814
3874
  this.execute(nodeId, { reason: "launch" });
3875
+ }
3815
3876
  }
3816
3877
  }
3817
3878
  if (startPaused) {
@@ -3958,10 +4019,15 @@ class GraphRuntime {
3958
4019
  ...opts,
3959
4020
  });
3960
4021
  this.graph.addNodeRunContextId(startNodeId, id);
3961
- this.execute(startNodeId, {
3962
- runContextIds: new Set([id]),
3963
- reason: opts?.reason ?? "runFromHereContext",
3964
- });
4022
+ try {
4023
+ this.execute(startNodeId, {
4024
+ runContextIds: new Set([id]),
4025
+ reason: opts?.reason ?? "runFromHereContext",
4026
+ });
4027
+ }
4028
+ finally {
4029
+ this.runContextManager.releaseScheduling(id);
4030
+ }
3965
4031
  });
3966
4032
  }
3967
4033
  setRunMode(runMode) {
@@ -4282,14 +4348,21 @@ class GraphRuntime {
4282
4348
  const val = this.getOutput(nodeId, handle);
4283
4349
  if (val !== undefined) {
4284
4350
  let runContextIdsToUse = undefined;
4351
+ let runContextIdToRelease;
4285
4352
  if (this.runMode === "manual") {
4286
- runContextIdsToUse = new Set([
4287
- this.runContextManager.createRunContext(nodeId, {
4288
- propagate: false,
4289
- }),
4290
- ]);
4353
+ runContextIdToRelease = this.runContextManager.createRunContext(nodeId, {
4354
+ propagate: false,
4355
+ });
4356
+ runContextIdsToUse = new Set([runContextIdToRelease]);
4357
+ }
4358
+ try {
4359
+ this.propagate(nodeId, handle, val, runContextIdsToUse);
4360
+ }
4361
+ finally {
4362
+ if (runContextIdToRelease) {
4363
+ this.runContextManager.releaseScheduling(runContextIdToRelease);
4364
+ }
4291
4365
  }
4292
- this.propagate(nodeId, handle, val, runContextIdsToUse);
4293
4366
  }
4294
4367
  }
4295
4368
  }