task_progress 0.0.1

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.
Files changed (107) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +29 -0
  3. data/LICENSE +21 -0
  4. data/MANUAL.md +851 -0
  5. data/README.md +158 -0
  6. data/RECIPES.md +413 -0
  7. data/config/importmap.rb +17 -0
  8. data/exe/task_progress +51 -0
  9. data/js/local_tasks.d.ts +44 -0
  10. data/js/local_tasks.js +229 -0
  11. data/js/package.json +31 -0
  12. data/js/paths.d.ts +5 -0
  13. data/js/paths.js +7 -0
  14. data/js/react_task_tree.d.ts +49 -0
  15. data/js/react_task_tree.js +123 -0
  16. data/js/render_queue.d.ts +24 -0
  17. data/js/render_queue.js +79 -0
  18. data/js/row_view.d.ts +25 -0
  19. data/js/row_view.js +69 -0
  20. data/js/stimulus_controller.d.ts +10 -0
  21. data/js/stimulus_controller.js +137 -0
  22. data/js/task_progress_clock.d.ts +2 -0
  23. data/js/task_progress_clock.js +33 -0
  24. data/js/task_tree.css +51 -0
  25. data/js/task_tree.d.ts +111 -0
  26. data/js/task_tree.js +444 -0
  27. data/lib/task_progress/assertions.rb +49 -0
  28. data/lib/task_progress/batch.rb +23 -0
  29. data/lib/task_progress/branch.rb +74 -0
  30. data/lib/task_progress/buffered_output.rb +63 -0
  31. data/lib/task_progress/cancellations_app.rb +76 -0
  32. data/lib/task_progress/event.rb +46 -0
  33. data/lib/task_progress/execution_log_output.rb +183 -0
  34. data/lib/task_progress/fold.rb +18 -0
  35. data/lib/task_progress/html_renderer.rb +153 -0
  36. data/lib/task_progress/job_serializer.rb +92 -0
  37. data/lib/task_progress/journal.rb +35 -0
  38. data/lib/task_progress/log.rb +263 -0
  39. data/lib/task_progress/memory_cache.rb +31 -0
  40. data/lib/task_progress/memory_stream_store.rb +142 -0
  41. data/lib/task_progress/mount.rb +448 -0
  42. data/lib/task_progress/ndjson_output.rb +24 -0
  43. data/lib/task_progress/node.rb +52 -0
  44. data/lib/task_progress/poll.rb +87 -0
  45. data/lib/task_progress/publishing_output.rb +30 -0
  46. data/lib/task_progress/ractor_output.rb +23 -0
  47. data/lib/task_progress/rails_defaults.rb +46 -0
  48. data/lib/task_progress/railtie.rb +91 -0
  49. data/lib/task_progress/recording_output.rb +51 -0
  50. data/lib/task_progress/redis_stream_store.rb +131 -0
  51. data/lib/task_progress/squasher.rb +59 -0
  52. data/lib/task_progress/sse.rb +114 -0
  53. data/lib/task_progress/state.rb +319 -0
  54. data/lib/task_progress/stream_output.rb +222 -0
  55. data/lib/task_progress/stream_reader.rb +76 -0
  56. data/lib/task_progress/summary_output.rb +37 -0
  57. data/lib/task_progress/terminal_output.rb +241 -0
  58. data/lib/task_progress/token.rb +58 -0
  59. data/lib/task_progress/tracked_io.rb +68 -0
  60. data/lib/task_progress/transaction.rb +41 -0
  61. data/lib/task_progress/trees_app.rb +66 -0
  62. data/lib/task_progress/turbo_helper.rb +18 -0
  63. data/lib/task_progress/turbo_sse.rb +103 -0
  64. data/lib/task_progress/turbo_stream_output.rb +61 -0
  65. data/lib/task_progress/version.rb +7 -0
  66. data/lib/task_progress/wire.rb +211 -0
  67. data/lib/task_progress.rb +1036 -0
  68. data/sig/generated/task_progress/assertions.rbs +36 -0
  69. data/sig/generated/task_progress/batch.rbs +33 -0
  70. data/sig/generated/task_progress/branch.rbs +47 -0
  71. data/sig/generated/task_progress/buffered_output.rbs +38 -0
  72. data/sig/generated/task_progress/cancellations_app.rbs +41 -0
  73. data/sig/generated/task_progress/event.rbs +20 -0
  74. data/sig/generated/task_progress/execution_log_output.rbs +93 -0
  75. data/sig/generated/task_progress/fold.rbs +25 -0
  76. data/sig/generated/task_progress/html_renderer.rbs +71 -0
  77. data/sig/generated/task_progress/journal.rbs +25 -0
  78. data/sig/generated/task_progress/log.rbs +140 -0
  79. data/sig/generated/task_progress/memory_cache.rbs +28 -0
  80. data/sig/generated/task_progress/memory_stream_store.rbs +83 -0
  81. data/sig/generated/task_progress/mount.rbs +200 -0
  82. data/sig/generated/task_progress/ndjson_output.rbs +18 -0
  83. data/sig/generated/task_progress/node.rbs +66 -0
  84. data/sig/generated/task_progress/poll.rbs +50 -0
  85. data/sig/generated/task_progress/publishing_output.rbs +24 -0
  86. data/sig/generated/task_progress/ractor_output.rbs +17 -0
  87. data/sig/generated/task_progress/rails_defaults.rbs +33 -0
  88. data/sig/generated/task_progress/recording_output.rbs +41 -0
  89. data/sig/generated/task_progress/redis_stream_store.rbs +75 -0
  90. data/sig/generated/task_progress/squasher.rbs +20 -0
  91. data/sig/generated/task_progress/sse.rbs +73 -0
  92. data/sig/generated/task_progress/state.rbs +165 -0
  93. data/sig/generated/task_progress/stream_output.rbs +142 -0
  94. data/sig/generated/task_progress/stream_reader.rbs +46 -0
  95. data/sig/generated/task_progress/summary_output.rbs +19 -0
  96. data/sig/generated/task_progress/terminal_output.rbs +117 -0
  97. data/sig/generated/task_progress/token.rbs +41 -0
  98. data/sig/generated/task_progress/tracked_io.rbs +48 -0
  99. data/sig/generated/task_progress/transaction.rbs +33 -0
  100. data/sig/generated/task_progress/trees_app.rbs +40 -0
  101. data/sig/generated/task_progress/turbo_helper.rbs +14 -0
  102. data/sig/generated/task_progress/turbo_sse.rbs +69 -0
  103. data/sig/generated/task_progress/turbo_stream_output.rbs +51 -0
  104. data/sig/generated/task_progress/version.rbs +6 -0
  105. data/sig/generated/task_progress/wire.rbs +102 -0
  106. data/sig/generated/task_progress.rbs +553 -0
  107. metadata +178 -0
@@ -0,0 +1,36 @@
1
+ # Generated from lib/task_progress/assertions.rb with RBS::Inline
2
+
3
+ class TaskProgress
4
+ # Assertions for Minitest - and ActiveSupport::TestCase, which is Minitest underneath - about
5
+ # what work reported. Each failure shows the whole tree, see TaskProgress#outline.
6
+ #
7
+ # class ImportTest < Minitest::Test
8
+ # include TaskProgress::Assertions
9
+ #
10
+ # def test_import
11
+ # import = TaskProgress.new("Import")
12
+ # Importer.new(progress: import).run
13
+ # assert_task_complete import
14
+ # assert_task_failed import.subtasks.first, with: /row 12/
15
+ # end
16
+ # end
17
+ module Assertions
18
+ def assert: (untyped, ?String) -> void
19
+
20
+ # The task is done, and everything under it ended
21
+ # : (TaskProgress, ?String?) -> void
22
+ def assert_task_complete: (TaskProgress, ?String?) -> void
23
+
24
+ # The task failed - with an error whose message matches `with`, if given
25
+ # : (TaskProgress, ?with: (String | Regexp)?, ?message: String?) -> void
26
+ def assert_task_failed: (TaskProgress, ?with: (String | Regexp)?, ?message: String?) -> void
27
+
28
+ # The task was canceled
29
+ # : (TaskProgress, ?String?) -> void
30
+ def assert_task_canceled: (TaskProgress, ?String?) -> void
31
+
32
+ # The tree, as a failure message ends with it
33
+ # : (TaskProgress) -> String
34
+ def self.tree: (TaskProgress) -> String
35
+ end
36
+ end
@@ -0,0 +1,33 @@
1
+ # Generated from lib/task_progress/batch.rb with RBS::Inline
2
+
3
+ class TaskProgress
4
+ # The unit of commit, see below
5
+ class Batch < Data
6
+ attr_reader log(): String?
7
+
8
+ attr_reader prev(): Integer
9
+
10
+ attr_reader seq(): Integer
11
+
12
+ attr_reader at(): Float
13
+
14
+ attr_reader events(): Array[Event]
15
+
16
+ def self.new: (String? log, Integer prev, Integer seq, Float at, Array[Event] events) -> instance
17
+ | (log: String?, prev: Integer, seq: Integer, at: Float, events: Array[Event]) -> instance
18
+
19
+ def self.members: () -> [ :log, :prev, :seq, :at, :events ]
20
+
21
+ def members: () -> [ :log, :prev, :seq, :at, :events ]
22
+ end
23
+
24
+ # The unit of commit: everything one transaction changed, in order. `seq` counts batches,
25
+ # not events, and `prev` names the batch it follows - so a reader that sees a `prev` other
26
+ # than the `seq` it last saw knows it missed something. `prev` 0 means "from the start",
27
+ # which is also how a squashed replay announces itself
28
+ class Batch
29
+ # Whether it starts over from nothing, as a squash does
30
+ # : () -> bool
31
+ def reset?: () -> bool
32
+ end
33
+ end
@@ -0,0 +1,47 @@
1
+ # Generated from lib/task_progress/branch.rb with RBS::Inline
2
+
3
+ class TaskProgress
4
+ # An output for one subtree, which it sees as a tree of its own: the branch's node becomes the
5
+ # root, `[*branch, "x3k0"]` becomes `["x3k0"]`, and batches that do not touch the branch are left
6
+ # out. That is a Mount run backwards - a Mount fed from a Branch grafts the subtree back whole.
7
+ #
8
+ # Since batches get left out, a branch numbers its own: `log` is the log's id with the branch
9
+ # path appended, and its `prev`/`seq` follow on from each other just like the log's do.
10
+ #
11
+ # Pruning an ancestor takes the branch's node away. Usually for good, since a pruned path is
12
+ # never given out again - and the branch then sends one last reset with nothing in it, and
13
+ # falls silent. But a Mount starting over (a remote that restarted, a stream resynced) prunes
14
+ # its subtree and puts the same tasks back in the same batch: the branch's node comes back,
15
+ # and the branch starts over with it.
16
+ #
17
+ # Its first batch always starts from nothing, `prev` 0: one for a node that comes into being
18
+ # after the branch was made carries the node's `open`
19
+ class Branch
20
+ # The output the branch feeds
21
+ attr_reader output: output
22
+
23
+ # The branch's task, in the whole tree
24
+ attr_reader path: path
25
+
26
+ # The log id its batches carry: the log's, with the path appended
27
+ attr_reader id: String
28
+
29
+ @seq: Integer
30
+
31
+ @gone: bool
32
+
33
+ @started: bool
34
+
35
+ # The log id of the branch at `path`
36
+ # : (String, path) -> String
37
+ def self.id: (String, path) -> String
38
+
39
+ # For an output, the branch at `path` of the log `log_id`, numbering on from `seq`
40
+ # : (output, path, String, Integer, ?started: bool) -> void
41
+ def initialize: (output, path, String, Integer, ?started: bool) -> void
42
+
43
+ # Passes on what of the batch is in the branch, moved to its root
44
+ # : (Batch, Log) -> void
45
+ def call: (Batch, Log) -> void
46
+ end
47
+ end
@@ -0,0 +1,38 @@
1
+ # Generated from lib/task_progress/buffered_output.rb with RBS::Inline
2
+
3
+ class TaskProgress
4
+ # Puts a thread between the log and a slow output. The first batch goes through at once,
5
+ # and whatever piles up while the output works (or during `interval` after it) goes out
6
+ # squashed into one - so a database write a few times a second keeps up with any number of
7
+ # workers, and never holds one of them up
8
+ class BufferedOutput
9
+ @output: output
10
+
11
+ @interval: Integer | Float
12
+
13
+ @queue: Thread::Queue[Batch]
14
+
15
+ @log: Log?
16
+
17
+ @squasher: Squasher
18
+
19
+ @thread: Thread
20
+
21
+ # For the slow output, sending to it at most every `interval` seconds
22
+ # : (output, ?interval: Integer | Float) -> void
23
+ def initialize: (output, ?interval: Integer | Float) -> void
24
+
25
+ # Takes one batch, to deliver from its own thread
26
+ # : (Batch, Log) -> void
27
+ def call: (Batch, Log) -> void
28
+
29
+ # Delivers what is still queued, then stops
30
+ # : () -> void
31
+ def close: () -> void
32
+
33
+ private
34
+
35
+ # : () -> void
36
+ def run: () -> void
37
+ end
38
+ end
@@ -0,0 +1,41 @@
1
+ # Generated from lib/task_progress/cancellations_app.rb with RBS::Inline
2
+
3
+ class TaskProgress
4
+ # A Rack app that takes the cancel buttons HTMLRenderer draws, for a tree that lives in another
5
+ # process - a job broadcasting with TaskProgress.broadcast. It cannot cancel anything itself:
6
+ # it checks the request's token, and leaves the request in the store the job watches. The job
7
+ # decides, as it does for any request (see TaskProgress#request_cancel), and the cancel comes
8
+ # back in its next frame.
9
+ #
10
+ # A Turbo form submission is answered with an empty Turbo Stream, which changes nothing on the
11
+ # page - the frame that follows does. Anything else gets 202 and a little JSON
12
+ class CancellationsApp
13
+ # How long a request waits for its run to take it up
14
+ REQUEST_TTL: Integer
15
+
16
+ @store: TurboStreamOutput::_Store
17
+
18
+ @tokens: Token
19
+
20
+ # Leaves requests in `store`, for tokens `tokens` issued
21
+ # : (store: TurboStreamOutput::_Store, tokens: Token) -> void
22
+ def initialize: (store: TurboStreamOutput::_Store, tokens: Token) -> void
23
+
24
+ # Takes a POST with `path` - the task's ids joined with a dash - and `token`, as a form or JSON
25
+ # : (Hash[String, untyped]) -> [Integer, Hash[String, String], Array[String]]
26
+ def call: (Hash[String, untyped]) -> [ Integer, Hash[String, String], Array[String] ]
27
+
28
+ # Where a request to cancel the task at `path` waits, for the tree stored under `key`
29
+ # : (String, path) -> String
30
+ def self.request_key: (String, path) -> String
31
+
32
+ # The request's parameters, from a form or JSON
33
+ # : (Hash[String, untyped]) -> Hash[String, untyped]
34
+ def self.params: (Hash[String, untyped]) -> Hash[String, untyped]
35
+
36
+ private
37
+
38
+ # : (Integer, Hash[Symbol, untyped]) -> [Integer, Hash[String, String], Array[String]]
39
+ def respond: (Integer, Hash[Symbol, untyped]) -> [ Integer, Hash[String, String], Array[String] ]
40
+ end
41
+ end
@@ -0,0 +1,20 @@
1
+ # Generated from lib/task_progress/event.rb with RBS::Inline
2
+
3
+ class TaskProgress
4
+ class Event < Data
5
+ attr_reader path: path
6
+ attr_reader op: Symbol
7
+ attr_reader value: untyped
8
+ def self.new: (path: path, op: Symbol, ?value: untyped) -> instance
9
+ def with: (?path: path, ?op: Symbol, ?value: untyped) -> instance
10
+ end
11
+
12
+ class Event
13
+ # An event for the node at `path` - its path is frozen, so the event can be shared
14
+ # : (path: path, op: Symbol, ?value: untyped) -> void
15
+ def initialize: (path: path, op: Symbol, ?value: untyped) -> void
16
+ end
17
+
18
+ # Every op there is - see Event
19
+ OPS: Array[Symbol]
20
+ end
@@ -0,0 +1,93 @@
1
+ # Generated from lib/task_progress/execution_log_output.rb with RBS::Inline
2
+
3
+ class TaskProgress
4
+ # For a CI log, or anything else that is read top to bottom after the fact: one finished
5
+ # line per thing worth saying, never a redraw, each line led by the overall percent and
6
+ # the time since the root opened.
7
+ #
8
+ # [ 42% 1m02s] Import › Rows: row 12000
9
+ # [ 50% 1m10s] ✓ Import › Rows 8.1s
10
+ # [ 50% 1m11s] ✗ Import › Engine: watermarking failed
11
+ # [ 50% 1m12s] ⊘ Import › Publish: Ctrl-C
12
+ #
13
+ # A task finishing, failing or being canceled, and a line it `puts`, go out at once and every
14
+ # time - though of a task and the ones under it ending in one batch, only the task says so. The
15
+ # rest - notes and counts - are only ever the latest news, so it says at most one of those
16
+ # per `interval`, the newest. With nothing to say for `heartbeat` it says it is still
17
+ # going, so a CI runner watching for output does not take a long quiet step for a hang
18
+ class ExecutionLogOutput
19
+ @ended: Set[path]
20
+
21
+ @thread: Thread?
22
+
23
+ @closed: bool
24
+
25
+ @said_at: Float
26
+
27
+ @pending: [ path, String? ]?
28
+
29
+ @log: Log?
30
+
31
+ @wake: Thread::ConditionVariable
32
+
33
+ @lock: Thread::Mutex
34
+
35
+ @clock: ^() -> Float
36
+
37
+ @heartbeat: Integer | Float
38
+
39
+ @interval: Integer | Float
40
+
41
+ @io: IO | StringIO
42
+
43
+ # Writes to `io`: news at most every `interval` seconds, and that it is still going after `heartbeat` seconds of quiet
44
+ # : (?IO | StringIO, ?interval: Integer | Float, ?heartbeat: Integer | Float, ?clock: ^() -> Float) -> void
45
+ def initialize: (?IO | StringIO, ?interval: Integer | Float, ?heartbeat: Integer | Float, ?clock: ^() -> Float) -> void
46
+
47
+ # Takes one batch, see Log#add_output
48
+ # : (Batch, Log) -> void
49
+ def call: (Batch, Log) -> void
50
+
51
+ # Says what is still pending, and stops
52
+ # : () -> void
53
+ def close: () -> void
54
+
55
+ private
56
+
57
+ # : () -> void
58
+ def run: () -> void
59
+
60
+ # : (?flush: bool) -> void
61
+ def close_locked: (?flush: bool) -> void
62
+
63
+ # : () -> void
64
+ def say_pending: () -> void
65
+
66
+ # : (State, String) -> void
67
+ def say: (State, String) -> void
68
+
69
+ ENDINGS: Array[Symbol]
70
+
71
+ PUT_ENDINGS: Hash[Symbol, Symbol]
72
+
73
+ # A squashed batch - from BufferedOutput, a stream, a mount catching up - says a task ended by
74
+ # putting it whole, finished. Said once, like the ending it stands for
75
+ # : (Event) -> Event?
76
+ def ending_put: (Event) -> Event?
77
+
78
+ # : (State, Event) -> String
79
+ def ending: (State, Event) -> String
80
+
81
+ # The task's name, after the names of every task it is under
82
+ # : (State, path) -> String
83
+ def names: (State, path) -> String
84
+
85
+ # : (Node) -> String
86
+ def count: (Node) -> String
87
+
88
+ # A task that reported nothing before it finished started when it finished, as far as the
89
+ # tree knows - so it took as long as it was open
90
+ # : (Node?) -> String
91
+ def took: (Node?) -> String
92
+ end
93
+ end
@@ -0,0 +1,25 @@
1
+ # Generated from lib/task_progress/fold.rb with RBS::Inline
2
+
3
+ class TaskProgress
4
+ # Siblings drawn as one row, see below
5
+ class Fold < Data
6
+ attr_reader status(): Symbol
7
+
8
+ attr_reader count(): Integer
9
+
10
+ def self.new: (Symbol status, Integer count) -> instance
11
+ | (status: Symbol, count: Integer) -> instance
12
+
13
+ def self.members: () -> [ :status, :count ]
14
+
15
+ def members: () -> [ :status, :count ]
16
+ end
17
+
18
+ # Siblings drawn as one row - "✓ 9,812 more done", "· 340 more waiting" - so a task with ten
19
+ # thousand children draws as a handful of rows. See State#rows, which makes them
20
+ class Fold
21
+ # What the row says
22
+ # : () -> String
23
+ def name: () -> String
24
+ end
25
+ end
@@ -0,0 +1,71 @@
1
+ # Generated from lib/task_progress/html_renderer.rb with RBS::Inline
2
+
3
+ class TaskProgress
4
+ # Draws a tree as HTML - the same rows, with the same classes, as the browser client's renderers
5
+ # draw, so `task_tree.css` styles both and a page can move from one to the other unchanged.
6
+ # This is what TurboStreamOutput sends, and what TurboSSE streams.
7
+ #
8
+ # Elapsed times are `<task-progress-clock>` elements (`task_progress_clock.js`), which tick on
9
+ # between frames; everything else is as it was when the tree was drawn. A cancelable task gets
10
+ # a cancel button that is a plain form, posting the task's path and `cancel_token` to
11
+ # `cancel_url` - so it needs no JavaScript, and Turbo submits it without leaving the page.
12
+ #
13
+ # To draw rows your own way, subclass it and override `row`, or hand TurboStreamOutput anything
14
+ # that answers `id` and `render(snapshot, from = [])` - say, an object that renders a partial
15
+ class HTMLRenderer
16
+ # A circle with a cross in it, drawn in the row's text color - the same in every font
17
+ CANCEL_ICON: String
18
+
19
+ GLYPHS: Hash[Symbol, String]
20
+
21
+ # The id of the element the tree is drawn in - what a Turbo Stream targets
22
+ attr_reader id: String
23
+
24
+ @clock: ^() -> Float
25
+
26
+ @cancel_token: String?
27
+
28
+ @cancel_url: String?
29
+
30
+ # Draws into an element with the id `id`. Without a `cancel_url`, no task offers a cancel
31
+ # button. `clock` is for tests to pin down
32
+ # : (id: String, ?cancel_url: String?, ?cancel_token: String?, ?clock: ^() -> Float) -> void
33
+ def initialize: (id: String, ?cancel_url: String?, ?cancel_token: String?, ?clock: ^() -> Float) -> void
34
+
35
+ # The whole tree in its element - or the subtree at `from`, drawn as a tree of its own
36
+ # : (State, ?path) -> String
37
+ def render: (State, ?path) -> String
38
+
39
+ # One task's row, `depth` levels in
40
+ # : (State, path, Node, ?depth: Integer) -> String
41
+ def row: (State, path, Node, ?depth: Integer) -> String
42
+
43
+ # Siblings folded into one row, see State#rows - drawn as a row like any other, so the
44
+ # columns line up
45
+ # : (path, Fold, ?depth: Integer) -> String
46
+ def fold: (path, Fold, ?depth: Integer) -> String
47
+
48
+ private
49
+
50
+ # : (String?) -> String
51
+ def h: (String?) -> String
52
+
53
+ # : (Node) -> String
54
+ def message: (Node) -> String
55
+
56
+ # The rate over the run so far, and the time left at it
57
+ # : (Node) -> String?
58
+ def pace: (Node) -> String?
59
+
60
+ # : (path, Node, Integer, bool) -> String
61
+ def measure: (path, Node, Integer, bool) -> String
62
+
63
+ # Ticks on in the browser between frames, from the time the tree was drawn
64
+ # : (Node) -> String
65
+ def clock: (Node) -> String
66
+
67
+ # The cancel column is there on every row, so the bars line up
68
+ # : (path, Node) -> String
69
+ def cancel: (path, Node) -> String
70
+ end
71
+ end
@@ -0,0 +1,25 @@
1
+ # Generated from lib/task_progress/journal.rb with RBS::Inline
2
+
3
+ class TaskProgress
4
+ # The changes one transaction made, so that it can be undone whole
5
+ class Journal
6
+ @saved: Hash[path, [ Node, Integer ]?]
7
+
8
+ # : () -> void
9
+ def initialize: () -> void
10
+
11
+ # Remembers the node as it was before the transaction first touched it - nil for one it opened.
12
+ # A shallow copy, and how many children it had: a transaction only ever adds children at the
13
+ # end, or swaps the list for a new one, so that is enough to put the list back - and a parent
14
+ # of a hundred thousand tasks is not copied whole every time one of them moves
15
+ # : (path, Node?) -> void
16
+ def touch: (path, Node?) -> void
17
+
18
+ # Every path it touched, opened or removed
19
+ # : () -> Array[path]
20
+ def paths: () -> Array[path]
21
+
22
+ # : (Hash[path, Node]) -> void
23
+ def rollback: (Hash[path, Node]) -> void
24
+ end
25
+ end
@@ -0,0 +1,140 @@
1
+ # Generated from lib/task_progress/log.rb with RBS::Inline
2
+
3
+ class TaskProgress
4
+ # The sequencer. Every change to a tree goes through `transaction`, which applies its events
5
+ # under the one lock and commits them as a numbered Batch - so the lock is what puts the
6
+ # changes from many threads into one order, and a batch is what crosses a process, a Ractor
7
+ # or the wire as one piece.
8
+ #
9
+ # Outputs get the batches strictly in order, but outside that lock: a worker never waits on
10
+ # another's output while it holds the tree. Delivery is synchronous to whoever committed, so
11
+ # a slow output slows its committer down - wrap it in BufferedOutput when it must not
12
+ class Log
13
+ # The id its batches carry - ten lower case characters, unless given
14
+ attr_reader id: String
15
+
16
+ # The seq of the last batch it committed
17
+ attr_reader seq: Integer
18
+
19
+ # The stream key the tree is written to or read from, when it is - see TaskProgress.handoff
20
+ attr_reader key: String?
21
+
22
+ # Lower case only, like the task ids, so neither can clash with another on a filesystem that
23
+ # ignores case (macOS, Windows) should one ever end up in a file name
24
+ ID_CHARS: Array[String]
25
+
26
+ @clock: ^() -> Float
27
+
28
+ @random: Random
29
+
30
+ @lock: Thread::Mutex
31
+
32
+ @delivery: Thread::Mutex
33
+
34
+ @state: State
35
+
36
+ @outputs: Array[output]
37
+
38
+ @outbox: Array[[ Batch, Array[output] ]]
39
+
40
+ @snapshot: State?
41
+
42
+ @touched: Set[path]
43
+
44
+ @on_cancel: Hash[path, Array[^(String?) -> void]]
45
+
46
+ @on_error: ^(Exception, untyped) -> void
47
+
48
+ @changed: Thread::ConditionVariable
49
+
50
+ @closers: Array[^() -> void]
51
+
52
+ # An empty log. `clock` gives the time to stamp batches with, and `random` draws task ids - both for tests to pin down
53
+ # : (?id: String | Symbol, ?key: String?, ?clock: ^() -> Float, ?random: Random) -> void
54
+ def initialize: (?id: String | Symbol, ?key: String?, ?clock: ^() -> Float, ?random: Random) -> void
55
+
56
+ # Hears about an output that raised, with the error and the output; it warns by default
57
+ attr_accessor on_error: ^(Exception, untyped) -> void
58
+
59
+ # Runs the block under the lock, collecting what it adds with `tx.<<`; it commits as one
60
+ # batch, or not at all if the block raises. A transaction opened inside another one on the
61
+ # same fiber joins it. The block holds the lock throughout - keep it short, and never wait
62
+ # in it on another thread that writes to this log. `at` is the time it happens at - now,
63
+ # unless it relays what happened elsewhere, earlier
64
+ # : [T] (?at: Float?) { (Transaction) -> T } -> T
65
+ def transaction: [T] (?at: Float?) { (Transaction) -> T } -> T
66
+
67
+ # Commits the events as one batch
68
+ # : (*Event) -> void
69
+ def append: (*Event) -> void
70
+
71
+ # An output is anything with `call(batch, log)`. Unless `replay: false`, it first gets the
72
+ # tree so far as one squashed batch, `prev` 0, and then every batch after it. With a `path`
73
+ # it gets only that subtree, as a tree of its own - see Branch
74
+ # : (?output?, ?replay: bool, ?path: path) ?{ (Batch, Log) -> void } -> output
75
+ def add_output: (?output?, ?replay: bool, ?path: path) ?{ (Batch, Log) -> void } -> output
76
+
77
+ # Takes the output as it was given to `add_output`. It hears nothing more once this returns -
78
+ # save a batch being delivered to it on another thread just then
79
+ # : (output) -> void
80
+ def remove_output: (output) -> void
81
+
82
+ # Calls the block with the reason once the task at `path` is canceled - at once, if it already
83
+ # was. A task that ends any other way drops it. The block runs on whichever thread delivers
84
+ # the cancel, after it has been committed: keep it short, and let it unblock the work (close a
85
+ # connection, raise into a thread) rather than do the work
86
+ # : (path) { (String?) -> void } -> void
87
+ def on_cancel: (path) { (String?) -> void } -> void
88
+
89
+ # A frozen copy of the tree, safe to read from anywhere - and from inside an output too,
90
+ # since outputs run outside the lock
91
+ # : () -> State
92
+ def snapshot: () -> State
93
+
94
+ # The tree so far as one batch of puts - what a late reader starts from. With a `path`, the
95
+ # subtree there, rooted at `[]` the way a Branch would send it
96
+ # : (?path) -> Batch
97
+ def squashed: (?path) -> Batch
98
+
99
+ # Waits up to `timeout` seconds for a batch after `seq` to commit, and returns the seq then
100
+ # : (Integer, Integer | Float) -> Integer
101
+ def wait_for_change: (Integer, Integer | Float) -> Integer
102
+
103
+ # Runs the block when the log is closed - to stop a thread that feeds it, or flush an output
104
+ # : () { () -> void } -> void
105
+ def on_close: () { () -> void } -> void
106
+
107
+ # Stops what was started for the tree - following a stream, beating, buffering - for when
108
+ # nobody needs it any more. The tree itself stays as it is
109
+ # : () -> void
110
+ def close: () -> void
111
+
112
+ # Its id and seq
113
+ # : () -> String
114
+ def inspect: () -> String
115
+
116
+ private
117
+
118
+ # : () -> Transaction?
119
+ def current: () -> Transaction?
120
+
121
+ # Thread#[] is fiber-local and, unlike Fiber storage, a new thread or fiber does not inherit
122
+ # it - which is what we want, since only the fiber that took the lock may join
123
+ # : (Transaction?) -> void
124
+ def current=: (Transaction?) -> void
125
+
126
+ # : (Batch) -> void
127
+ def call_cancel_callbacks: (Batch) -> void
128
+
129
+ # : (Transaction) -> void
130
+ def commit_locked: (Transaction) -> void
131
+
132
+ # One deliverer at a time, taking the outbox in order. A commit made from inside an output
133
+ # finds its own fiber already delivering, and leaves its batch for the loop it is inside of
134
+ #
135
+ # Each batch goes to all its outputs with interrupts held off, so one can never reach some
136
+ # outputs and not others. An interrupt waits for the batch in hand, not for the whole outbox
137
+ # : () -> void
138
+ def deliver: () -> void
139
+ end
140
+ end
@@ -0,0 +1,28 @@
1
+ # Generated from lib/task_progress/memory_cache.rb with RBS::Inline
2
+
3
+ class TaskProgress
4
+ # A store kept in this process, for where `Rails.cache` is not to hand - tests, a Rack app that
5
+ # runs its jobs in-process. Anything with `read`, `write` and `delete` will do as a store; this
6
+ # is the smallest one, and is safe to share between threads
7
+ class MemoryCache
8
+ @entries: Hash[String, untyped]
9
+
10
+ @lock: Thread::Mutex
11
+
12
+ # An empty store
13
+ # : () -> void
14
+ def initialize: () -> void
15
+
16
+ # What was written under `key`, or nil
17
+ # : (String) -> untyped
18
+ def read: (String) -> untyped
19
+
20
+ # Keeps `value` under `key`; options, such as `expires_in:`, are taken and ignored
21
+ # : (String, untyped, **untyped) -> void
22
+ def write: (String, untyped, **untyped) -> void
23
+
24
+ # Forgets what is under `key`
25
+ # : (String) -> void
26
+ def delete: (String) -> void
27
+ end
28
+ end