Skip to content

Make stage execution typed end to end - #47

Merged
vim89 merged 4 commits into
mainfrom
typed-stage-chain
Oct 6, 2026
Merged

vim89 merged 4 commits into
mainfrom
typed-stage-chain

Conversation

@vim89

@vim89 vim89 commented Oct 6, 2026

Copy link
Copy Markdown
Owner

Stages are now held in a StageChain that carries the type between each pair of stages, so StageComposer builds the pipeline arrow without the two Kleisli[F, Any, Any] casts.

Stage counts in metadata now come from the kind a builder gives each stage instead of five different per-builder checks.

@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 6, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-10-06T15:14:40.494742Z af1d8bc PR opened
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: af1d8bc43e

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".


// Computed once per append rather than per read: `names`, `size` and `count` are all called while a
// pipeline is being built, and the next stage's name is derived from `size`.
private[exec] val entries: List[Entry] = init.entries :+ entry

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Store stage metadata with structural sharing

When stages are generated dynamically, every Link retains its predecessor while also copying all of init.entries via :+; a chain of n stages therefore permanently retains O(n²) list nodes. Large generated pipelines can consume excessive heap or fail during construction, so store entries in a structurally shared order (for example, prepend and reverse only when metadata is requested).

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 5e38625. Entries are now held most-recent-first, so a link conses onto the preceding spine instead of copying it: appending is constant time and the whole chain holds one list. size is a per-link val because a builder reads it on every append to name the next stage. names is the only caller that needs running order, and it reverses once per build.

private[exec] def arrow(
decorate: StageDecorator[F],
)(implicit F: EffectSystem[F],
): Kleisli[F, In, Out] = init.arrow(decorate).andThen(decorate(entry.name, stage))

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Build deep stage chains without recursive descent

When a pipeline contains thousands of programmatically generated stages, arrow recursively descends through one JVM stack frame per Link before creating the effect. The previous list-based implementation used foldLeft, so sufficiently deep pipelines now throw StackOverflowError before any stage executes; construct the composed arrow with a stack-safe traversal.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Measured rather than assumed: 10k stages build and run in 12ms, 50k overflow. So the bound is real, and it is being accepted rather than fixed.

Folding left needs the chain reversed, and reversing a type-aligned sequence needs the existential cast this type exists to remove, so a stack-safe build would reintroduce exactly what the PR removes. The bound is now documented on arrow and StageChainSpec pins 10k, which also guards the entries fix. For context, every stage carries a span and a START/COMPLETE pair, so a chain anywhere near the bound is unusable for reasons unrelated to the stack.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Correcting my earlier reply on this thread: the numbers I gave were measured on macOS, whose main thread gets 8MB of stack. CI proved them wrong. The 10k test passed on macos-latest and overflowed on ubuntu and windows (~1MB stacks), and on a 512k stack the eager walk overflowed between 2k and 4k. So the bound was not "far past any pipeline" and it was not a property of the chain at all, it was a property of whichever thread happened to run it.

Fixed rather than documented, in 07e1242. Each Link.arrow now defers the rest of the chain through EffectSystem.suspend instead of building it with andThen, so the descent happens inside F s own loop. No casts added. The test now runs 50k stages on a thread with an explicit 512k stack, which the old eager version could not survive, so the property is pinned to a named stack size rather than to the runner.

Two costs, both stated in the scaladoc: the links are walked once per run rather than once per build, and stack safety now rests on F.flatMap and F.suspend being stack-safe, which MonadError does not promise. Thanks for pushing on this one.

@vim89
vim89 merged commit e296c81 into main Oct 6, 2026
15 checks passed
@vim89
vim89 deleted the typed-stage-chain branch October 6, 2026 16:08
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant