Skip to content

Conversation

@adrianlyjak
Copy link
Contributor

@adrianlyjak adrianlyjak commented Jan 30, 2026

Splitting off some changes from DBOS work

Control Loop Changes

  • Switches out the asyncio delay mechanism for a pull-with-timeout that is more deterministic friendly
  • Adds a priority queue of delayed tasks
  • Switches out the misc firing /spawning of async tasks to a more rigorous pattern where tasks are only created in the main loop, and gathered in one location. This makes the concurrency more straightforward to reason about
  • Moves the "selecting" of which tasks is completed first into an adapter responsibility, this allows more granular journaling decisions (currently needed to make DBOS determinism work. We end up keeping our own lightweight journal as a workaround until we can figure out a better way to integration)
  • adds a named task around the task to make journaling easier

Misc Changs

  • Removes mypy. 3 type checkers is too many
  • Starts a place for runtime matrix tests. I put these in the integration tests as I expect they will frequently be slow
  • Reduces some test warning changes
  • Fix bug where run_id is not actually passed through from workflow.run

Open with Devin

@changeset-bot
Copy link

changeset-bot bot commented Jan 30, 2026

🦋 Changeset detected

Latest commit: 9076a24

The changes in this PR will be included in the next version bump.

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@adrianlyjak adrianlyjak changed the base branch from main to adrian/runtime-split January 30, 2026 23:20
@adrianlyjak adrianlyjak force-pushed the adrian/ticky branch 6 times, most recently from 4b797ce to 8c4d60a Compare January 31, 2026 00:07
@adrianlyjak adrianlyjak marked this pull request as ready for review January 31, 2026 00:07
Base automatically changed from adrian/runtime-split to main January 31, 2026 23:19
@coveralls
Copy link

Pull Request Test Coverage Report for Build 21552592448

Details

  • 0 of 0 changed or added relevant lines in 0 files are covered.
  • 52 unchanged lines in 5 files lost coverage.
  • Overall coverage decreased (-0.2%) to 87.856%

Files with Coverage Reduction New Missed Lines %
workflows/context/internal_context.py 1 90.91%
workflows/runtime/types/plugin.py 4 96.06%
workflows/workflow.py 5 97.67%
workflows/plugins/basic.py 11 92.73%
workflows/runtime/control_loop.py 31 91.26%
Totals Coverage Status
Change from base Build 21552583841: -0.2%
Covered Lines: 3270
Relevant Lines: 3722

💛 - Coveralls

@adrianlyjak adrianlyjak force-pushed the adrian/ticky branch 3 times, most recently from cb8cc07 to e95d13d Compare February 2, 2026 20:18
Copy link

@devin-ai-integration devin-ai-integration bot left a comment

Choose a reason for hiding this comment

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

✅ Devin Review: No Issues Found

Devin Review analyzed this PR and found no potential bugs to report.

View in Devin Review to see 8 additional flags.

Open in Devin Review

Copy link

@devin-ai-integration devin-ai-integration bot left a comment

Choose a reason for hiding this comment

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

Devin Review found 1 new potential issue.

View issue and 10 additional flags in Devin Review.

Open in Devin Review

return await self.queue.get()
def schedule_tick(self, tick: WorkflowTick, at_time: float) -> None:
"""Schedule a tick to be processed at a specific time."""
heapq.heappush(self.scheduled_wakeups, (at_time, tick))

Choose a reason for hiding this comment

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

🔴 Heap comparison TypeError when two ticks are scheduled at the same time

The scheduled_wakeups heap stores tuples of (float, WorkflowTick). When two ticks are scheduled at exactly the same timestamp, Python's heapq will try to compare the WorkflowTick objects as a tiebreaker. However, WorkflowTick dataclasses (defined in workflows/runtime/types/ticks.py:27-70) don't implement __lt__, causing a TypeError: '<' not supported between instances.

Click to expand

How it triggers

At control_loop.py:144, heapq.heappush(self.scheduled_wakeups, (at_time, tick)) is called. If two ticks have the same at_time value, the heap will attempt to compare the ticks:

# control_loop.py:144
heapq.heappush(self.scheduled_wakeups, (at_time, tick))

This scenario can occur when:

  1. Multiple delayed events are scheduled at the same time (e.g., retry delays)
  2. Timeout and another tick happen to have identical timestamps

Reproduction

import heapq
from dataclasses import dataclass

@dataclass(frozen=True)
class TickTimeout:
    timeout: float

heap = []
heapq.heappush(heap, (1.0, TickTimeout(timeout=1.0)))
heapq.heappush(heap, (1.0, TickTimeout(timeout=2.0)))  # Same timestamp
# TypeError: '<' not supported between instances of 'TickTimeout' and 'TickTimeout'

Impact

Workflow crashes with TypeError when two events happen to be scheduled at the exact same time.

Recommendation: Add a sequence number as a tiebreaker in the tuple: (at_time, sequence_counter, tick). Increment the counter on each push. Alternatively, wrap ticks in a class that implements __lt__ based on insertion order.

Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Comment on lines +77 to +82
class _DelayedTick:
"""Sentinel class representing a tick that should be scheduled after a delay."""

def __init__(self, tick: WorkflowTick, delay: float) -> None:
self.tick = tick
self.delay = delay

Choose a reason for hiding this comment

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

🚩 Dead code: _DelayedTick class is defined but never used

The _DelayedTick class at control_loop.py:77-82 is defined but never instantiated or referenced anywhere in the codebase. This appears to be leftover code from the refactoring. The control loop now uses schedule_tick() with a heap-based approach instead. This is harmless but could be cleaned up.

Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

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.

3 participants