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.
- checksums.yaml +7 -0
- data/CHANGELOG.md +29 -0
- data/LICENSE +21 -0
- data/MANUAL.md +851 -0
- data/README.md +158 -0
- data/RECIPES.md +413 -0
- data/config/importmap.rb +17 -0
- data/exe/task_progress +51 -0
- data/js/local_tasks.d.ts +44 -0
- data/js/local_tasks.js +229 -0
- data/js/package.json +31 -0
- data/js/paths.d.ts +5 -0
- data/js/paths.js +7 -0
- data/js/react_task_tree.d.ts +49 -0
- data/js/react_task_tree.js +123 -0
- data/js/render_queue.d.ts +24 -0
- data/js/render_queue.js +79 -0
- data/js/row_view.d.ts +25 -0
- data/js/row_view.js +69 -0
- data/js/stimulus_controller.d.ts +10 -0
- data/js/stimulus_controller.js +137 -0
- data/js/task_progress_clock.d.ts +2 -0
- data/js/task_progress_clock.js +33 -0
- data/js/task_tree.css +51 -0
- data/js/task_tree.d.ts +111 -0
- data/js/task_tree.js +444 -0
- data/lib/task_progress/assertions.rb +49 -0
- data/lib/task_progress/batch.rb +23 -0
- data/lib/task_progress/branch.rb +74 -0
- data/lib/task_progress/buffered_output.rb +63 -0
- data/lib/task_progress/cancellations_app.rb +76 -0
- data/lib/task_progress/event.rb +46 -0
- data/lib/task_progress/execution_log_output.rb +183 -0
- data/lib/task_progress/fold.rb +18 -0
- data/lib/task_progress/html_renderer.rb +153 -0
- data/lib/task_progress/job_serializer.rb +92 -0
- data/lib/task_progress/journal.rb +35 -0
- data/lib/task_progress/log.rb +263 -0
- data/lib/task_progress/memory_cache.rb +31 -0
- data/lib/task_progress/memory_stream_store.rb +142 -0
- data/lib/task_progress/mount.rb +448 -0
- data/lib/task_progress/ndjson_output.rb +24 -0
- data/lib/task_progress/node.rb +52 -0
- data/lib/task_progress/poll.rb +87 -0
- data/lib/task_progress/publishing_output.rb +30 -0
- data/lib/task_progress/ractor_output.rb +23 -0
- data/lib/task_progress/rails_defaults.rb +46 -0
- data/lib/task_progress/railtie.rb +91 -0
- data/lib/task_progress/recording_output.rb +51 -0
- data/lib/task_progress/redis_stream_store.rb +131 -0
- data/lib/task_progress/squasher.rb +59 -0
- data/lib/task_progress/sse.rb +114 -0
- data/lib/task_progress/state.rb +319 -0
- data/lib/task_progress/stream_output.rb +222 -0
- data/lib/task_progress/stream_reader.rb +76 -0
- data/lib/task_progress/summary_output.rb +37 -0
- data/lib/task_progress/terminal_output.rb +241 -0
- data/lib/task_progress/token.rb +58 -0
- data/lib/task_progress/tracked_io.rb +68 -0
- data/lib/task_progress/transaction.rb +41 -0
- data/lib/task_progress/trees_app.rb +66 -0
- data/lib/task_progress/turbo_helper.rb +18 -0
- data/lib/task_progress/turbo_sse.rb +103 -0
- data/lib/task_progress/turbo_stream_output.rb +61 -0
- data/lib/task_progress/version.rb +7 -0
- data/lib/task_progress/wire.rb +211 -0
- data/lib/task_progress.rb +1036 -0
- data/sig/generated/task_progress/assertions.rbs +36 -0
- data/sig/generated/task_progress/batch.rbs +33 -0
- data/sig/generated/task_progress/branch.rbs +47 -0
- data/sig/generated/task_progress/buffered_output.rbs +38 -0
- data/sig/generated/task_progress/cancellations_app.rbs +41 -0
- data/sig/generated/task_progress/event.rbs +20 -0
- data/sig/generated/task_progress/execution_log_output.rbs +93 -0
- data/sig/generated/task_progress/fold.rbs +25 -0
- data/sig/generated/task_progress/html_renderer.rbs +71 -0
- data/sig/generated/task_progress/journal.rbs +25 -0
- data/sig/generated/task_progress/log.rbs +140 -0
- data/sig/generated/task_progress/memory_cache.rbs +28 -0
- data/sig/generated/task_progress/memory_stream_store.rbs +83 -0
- data/sig/generated/task_progress/mount.rbs +200 -0
- data/sig/generated/task_progress/ndjson_output.rbs +18 -0
- data/sig/generated/task_progress/node.rbs +66 -0
- data/sig/generated/task_progress/poll.rbs +50 -0
- data/sig/generated/task_progress/publishing_output.rbs +24 -0
- data/sig/generated/task_progress/ractor_output.rbs +17 -0
- data/sig/generated/task_progress/rails_defaults.rbs +33 -0
- data/sig/generated/task_progress/recording_output.rbs +41 -0
- data/sig/generated/task_progress/redis_stream_store.rbs +75 -0
- data/sig/generated/task_progress/squasher.rbs +20 -0
- data/sig/generated/task_progress/sse.rbs +73 -0
- data/sig/generated/task_progress/state.rbs +165 -0
- data/sig/generated/task_progress/stream_output.rbs +142 -0
- data/sig/generated/task_progress/stream_reader.rbs +46 -0
- data/sig/generated/task_progress/summary_output.rbs +19 -0
- data/sig/generated/task_progress/terminal_output.rbs +117 -0
- data/sig/generated/task_progress/token.rbs +41 -0
- data/sig/generated/task_progress/tracked_io.rbs +48 -0
- data/sig/generated/task_progress/transaction.rbs +33 -0
- data/sig/generated/task_progress/trees_app.rbs +40 -0
- data/sig/generated/task_progress/turbo_helper.rbs +14 -0
- data/sig/generated/task_progress/turbo_sse.rbs +69 -0
- data/sig/generated/task_progress/turbo_stream_output.rbs +51 -0
- data/sig/generated/task_progress/version.rbs +6 -0
- data/sig/generated/task_progress/wire.rbs +102 -0
- data/sig/generated/task_progress.rbs +553 -0
- metadata +178 -0
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
# Generated from lib/task_progress/sse.rb with RBS::Inline
|
|
2
|
+
|
|
3
|
+
class TaskProgress
|
|
4
|
+
# A Rack response body streaming a tree as Server-Sent Events: first the tree so far, squashed
|
|
5
|
+
# into one batch with `prev` 0, then every batch as it commits - each in a `batch` event whose
|
|
6
|
+
# `id:` is its seq. A browser that reconnects starts over from a fresh squash, which is always
|
|
7
|
+
# right and costs a few puts. The stream ends once the root is finished. Given a subtask, it
|
|
8
|
+
# streams only that task's subtree, with the subtask as the root.
|
|
9
|
+
#
|
|
10
|
+
# # Rack
|
|
11
|
+
# TaskProgress::SSE.response(task)
|
|
12
|
+
# # Rails
|
|
13
|
+
# headers.merge!(TaskProgress::SSE::HEADERS)
|
|
14
|
+
# self.response_body = TaskProgress::SSE.new(task)
|
|
15
|
+
#
|
|
16
|
+
# Given a key instead of a task - a tree handed off with TaskProgress.handoff - it follows that
|
|
17
|
+
# tree from the stream for as long as it streams, and stops following when the stream ends or
|
|
18
|
+
# the server closes it. `response` answers 404 for a key with nothing at it
|
|
19
|
+
class SSE
|
|
20
|
+
# What an SSE response is sent with
|
|
21
|
+
HEADERS: Hash[String, String]
|
|
22
|
+
|
|
23
|
+
@source: TaskProgress | Log
|
|
24
|
+
|
|
25
|
+
@heartbeat: Integer | Float
|
|
26
|
+
|
|
27
|
+
@reconnect: Integer | Float
|
|
28
|
+
|
|
29
|
+
@close: bool
|
|
30
|
+
|
|
31
|
+
# A whole Rack response: status, headers and this body - or a 404, for a key with nothing at it
|
|
32
|
+
# : (TaskProgress | Log | String, **untyped) -> [Integer, Hash[String, String], SSE | Array[String]]
|
|
33
|
+
def self.response: (TaskProgress | Log | String, **untyped) -> [ Integer, Hash[String, String], SSE | Array[String] ]
|
|
34
|
+
|
|
35
|
+
# One batch as an SSE event
|
|
36
|
+
# : (Batch) -> String
|
|
37
|
+
def self.frame: (Batch) -> String
|
|
38
|
+
|
|
39
|
+
# Streams the task - its subtree only, for a subtask - or the tree handed off at a key, from
|
|
40
|
+
# `stream`. It pings every `heartbeat` seconds of quiet, and tells the browser to reconnect
|
|
41
|
+
# after `reconnect` seconds
|
|
42
|
+
# : (TaskProgress | Log | String, ?stream_store: untyped, ?heartbeat: Integer | Float, ?reconnect: Integer | Float) -> void
|
|
43
|
+
def initialize: (TaskProgress | Log | String, ?stream_store: untyped, ?heartbeat: Integer | Float, ?reconnect: Integer | Float) -> void
|
|
44
|
+
|
|
45
|
+
# Stops following a tree it followed by its key - Rack calls it when the response is over
|
|
46
|
+
# : () -> void
|
|
47
|
+
def close: () -> void
|
|
48
|
+
|
|
49
|
+
# Yields the stream a chunk at a time, until the task finishes - as a Rack body does
|
|
50
|
+
# : () { (String) -> void } -> void
|
|
51
|
+
def each: () { (String) -> void } -> void
|
|
52
|
+
|
|
53
|
+
# How many batches wait for a slow client before it gets a squash instead
|
|
54
|
+
BACKLOG: Integer
|
|
55
|
+
|
|
56
|
+
private
|
|
57
|
+
|
|
58
|
+
# : () -> Log
|
|
59
|
+
def log: () -> Log
|
|
60
|
+
|
|
61
|
+
# : () -> path
|
|
62
|
+
def path: () -> path
|
|
63
|
+
|
|
64
|
+
# : () -> bool
|
|
65
|
+
def present?: () -> bool
|
|
66
|
+
|
|
67
|
+
# Judged by what was sent, never by the snapshot: that runs ahead of delivery, and ending on
|
|
68
|
+
# it could cut the stream before the batch that finished the root went out. An empty reset
|
|
69
|
+
# is a branch whose task was pruned, which will not come back either
|
|
70
|
+
# : (Batch) -> bool
|
|
71
|
+
def finishes_root?: (Batch) -> bool
|
|
72
|
+
end
|
|
73
|
+
end
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
# Generated from lib/task_progress/state.rb with RBS::Inline
|
|
2
|
+
|
|
3
|
+
class TaskProgress
|
|
4
|
+
# The tree as it stands after a run of events: what `apply` folds them into. It checks every
|
|
5
|
+
# event before it changes anything, so a bad one raises and leaves the state as it was. Not
|
|
6
|
+
# thread-safe on its own - the Log holds its lock around every apply
|
|
7
|
+
class State
|
|
8
|
+
# What an `open` or a `meta` may set
|
|
9
|
+
ATTRIBUTES: Array[Symbol]
|
|
10
|
+
|
|
11
|
+
CHILD_IDS: Integer
|
|
12
|
+
|
|
13
|
+
# Siblings past this many are folded by `rows`...
|
|
14
|
+
FOLD_OVER: Integer
|
|
15
|
+
|
|
16
|
+
# ...keeping this many of the done ones, the latest to finish, and of the ones waiting, the first
|
|
17
|
+
FOLD_KEEP: Integer
|
|
18
|
+
|
|
19
|
+
ENDINGS: Hash[Symbol, Symbol]
|
|
20
|
+
|
|
21
|
+
# What a `put` carries - a Node, less what never goes over the wire
|
|
22
|
+
FIELDS: Array[Symbol]
|
|
23
|
+
|
|
24
|
+
@nodes: Hash[path, Node]
|
|
25
|
+
|
|
26
|
+
# A new node, opened at `at`, before its attributes are set
|
|
27
|
+
# : (Float) -> Node
|
|
28
|
+
def self.blank_node: (Float) -> Node
|
|
29
|
+
|
|
30
|
+
# Folding the squash of a state gives that state back, which is what makes a squashed log
|
|
31
|
+
# interchangeable with the one it came from
|
|
32
|
+
# : (Array[Event], ?at: Float) -> State
|
|
33
|
+
def self.fold: (Array[Event], ?at: Float) -> State
|
|
34
|
+
|
|
35
|
+
# An empty tree - or one made of these nodes
|
|
36
|
+
# : (?Hash[path, Node]) -> void
|
|
37
|
+
def initialize: (?Hash[path, Node]) -> void
|
|
38
|
+
|
|
39
|
+
# The node at `path`, or nil
|
|
40
|
+
# : (path) -> Node?
|
|
41
|
+
def []: (path) -> Node?
|
|
42
|
+
|
|
43
|
+
# Whether there is a node at `path`
|
|
44
|
+
# : (path) -> bool
|
|
45
|
+
def key?: (path) -> bool
|
|
46
|
+
|
|
47
|
+
# Whether it has no root yet
|
|
48
|
+
# : () -> bool
|
|
49
|
+
def empty?: () -> bool
|
|
50
|
+
|
|
51
|
+
# How many nodes it has
|
|
52
|
+
# : () -> Integer
|
|
53
|
+
def size: () -> Integer
|
|
54
|
+
|
|
55
|
+
# The paths of the nodes directly under `path`
|
|
56
|
+
# : (path) -> Array[path]
|
|
57
|
+
def children: (path) -> Array[path]
|
|
58
|
+
|
|
59
|
+
# A path for a new child of `path`. A child is named by a random id rather than its position,
|
|
60
|
+
# so a pruned task's path never turns up again on another one - not after a prune here, nor
|
|
61
|
+
# when a restarted remote replaces its tree. Ids only have to differ between siblings, since
|
|
62
|
+
# the path tells cousins apart, and a draw that hits a sibling is drawn again
|
|
63
|
+
# : (path, Random) -> path
|
|
64
|
+
def new_child: (path, Random) -> path
|
|
65
|
+
|
|
66
|
+
# Pre-order, children in the order they opened - the order a renderer wants and the order
|
|
67
|
+
# puts replay in, so a reader rebuilding from a squash ends up with the same order
|
|
68
|
+
# : (?path) { (path, Node) -> void } -> void
|
|
69
|
+
# : (?path) -> Enumerator[[path, Node], void]
|
|
70
|
+
def each: (?path) { (path, Node) -> void } -> void
|
|
71
|
+
| (?path) -> Enumerator[[ path, Node ], void]
|
|
72
|
+
|
|
73
|
+
# What to draw, in the order of `each` - but where a task has more than `fold_over` children,
|
|
74
|
+
# the ones that are done (save the last `keep` to finish) are one Fold row ahead of the rest,
|
|
75
|
+
# and the ones still waiting (save the first `keep`) one Fold row after them. What is
|
|
76
|
+
# running, failed or canceled is always drawn. A Fold comes with its parent's path and
|
|
77
|
+
# `~done` or `~pending` after it, which no task id can be
|
|
78
|
+
# : (?path, ?fold_over: Integer, ?keep: Integer) { (path, Node | Fold) -> void } -> void
|
|
79
|
+
# : (?path, ?fold_over: Integer, ?keep: Integer) -> Enumerator[[path, Node | Fold], void]
|
|
80
|
+
def rows: (?path, ?fold_over: Integer, ?keep: Integer) { (path, Node | Fold) -> void } -> void
|
|
81
|
+
| (?path, ?fold_over: Integer, ?keep: Integer) -> Enumerator[[ path, Node | Fold ], void]
|
|
82
|
+
|
|
83
|
+
# Folds one event in, or raises ArgumentError and leaves the state as it was
|
|
84
|
+
# : (Event, at: Float, ?journal: Journal?) -> self
|
|
85
|
+
def apply: (Event, at: Float, ?journal: Journal?) -> self
|
|
86
|
+
|
|
87
|
+
# The tasks under `path` that have not ended, in pre-order - so reversed, every task comes
|
|
88
|
+
# before the one it is under. A mounted subtree is left out unless `mounts` - a remote that
|
|
89
|
+
# has not finished may yet, so a `done` from here would be a guess, but calling it off is not
|
|
90
|
+
# : (path, ?mounts: bool) -> Array[path]
|
|
91
|
+
def open_under: (path, ?mounts: bool) -> Array[path]
|
|
92
|
+
|
|
93
|
+
# The state as a list of puts, one per node, in pre-order - or just the subtree at `path`
|
|
94
|
+
# : (?path) -> Array[Event]
|
|
95
|
+
def squash: (?path) -> Array[Event]
|
|
96
|
+
|
|
97
|
+
# Done, and everything under it ended - each task by a `done`, `fail` or `cancel` of its own.
|
|
98
|
+
# Children that are all through do not finish their parent, which may have more to do after
|
|
99
|
+
# them; a failed or canceled one does not hold back a parent that went on to finish regardless
|
|
100
|
+
# : (?path, ?Node?) -> bool
|
|
101
|
+
def complete?: (?path, ?Node?) -> bool
|
|
102
|
+
|
|
103
|
+
# 0.0..1.0, unrounded. Children decide a parent's progress, by weight, and children it was
|
|
104
|
+
# told to expect but has not got yet count as zero. A leaf goes by its count when it has a
|
|
105
|
+
# total, and by its percent otherwise
|
|
106
|
+
# : (?path, ?Node?) -> Float
|
|
107
|
+
def fraction: (?path, ?Node?) -> Float
|
|
108
|
+
|
|
109
|
+
# A whole percent, and 100 only once everything under it is done: 99.5 does not round up
|
|
110
|
+
# into a bar that reads as finished while work is still running
|
|
111
|
+
# : (?path) -> Integer
|
|
112
|
+
def percent: (?path) -> Integer
|
|
113
|
+
|
|
114
|
+
# Every node, as a `put` carries it
|
|
115
|
+
# : () -> Hash[path, Hash[Symbol, untyped]]
|
|
116
|
+
def to_h: () -> Hash[path, Hash[Symbol, untyped]]
|
|
117
|
+
|
|
118
|
+
# A frozen copy, safe to read from any thread. Given an earlier snapshot of this state and the
|
|
119
|
+
# paths changed since, it copies only those, and takes the rest from the earlier one
|
|
120
|
+
# : (?State?, ?Enumerable[path]?) -> State
|
|
121
|
+
def snapshot: (?State?, ?Enumerable[path]?) -> State
|
|
122
|
+
|
|
123
|
+
# A copy that can change without changing this one
|
|
124
|
+
# : () -> State
|
|
125
|
+
def dup: () -> State
|
|
126
|
+
|
|
127
|
+
# Undoes what one transaction did
|
|
128
|
+
# : (Journal) -> void
|
|
129
|
+
def rollback: (Journal) -> void
|
|
130
|
+
|
|
131
|
+
# The nodes themselves, for an earlier snapshot to lend a new one what has not changed
|
|
132
|
+
# : () -> Hash[path, Node]
|
|
133
|
+
attr_reader nodes: Hash[path, Node]
|
|
134
|
+
|
|
135
|
+
private
|
|
136
|
+
|
|
137
|
+
# : (Node) -> Node
|
|
138
|
+
def frozen: (Node) -> Node
|
|
139
|
+
|
|
140
|
+
# : (Node) -> Integer
|
|
141
|
+
def missing: (Node) -> Integer
|
|
142
|
+
|
|
143
|
+
# : (path) -> Node?
|
|
144
|
+
def parent_of!: (path) -> Node?
|
|
145
|
+
|
|
146
|
+
# : (path, Node?) -> Node
|
|
147
|
+
def existing!: (path, Node?) -> Node
|
|
148
|
+
|
|
149
|
+
# : (path, Node?) -> Node
|
|
150
|
+
def updatable!: (path, Node?) -> Node
|
|
151
|
+
|
|
152
|
+
# Work started anywhere starts everything above it too
|
|
153
|
+
# : (path, Float, Journal?) -> void
|
|
154
|
+
def start: (path, Float, Journal?) -> void
|
|
155
|
+
|
|
156
|
+
# : (Node, Hash[Symbol, untyped]) -> void
|
|
157
|
+
def assign: (Node, Hash[Symbol, untyped]) -> void
|
|
158
|
+
|
|
159
|
+
# : (path) -> path
|
|
160
|
+
def up: (path) -> path
|
|
161
|
+
|
|
162
|
+
# : (Node, path) -> void
|
|
163
|
+
def adopt: (Node, path) -> void
|
|
164
|
+
end
|
|
165
|
+
end
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
# Generated from lib/task_progress/stream_output.rb with RBS::Inline
|
|
2
|
+
|
|
3
|
+
class TaskProgress
|
|
4
|
+
# Writes a tree to a stream - a MemoryStreamStore, a RedisStreamStore - for readers anywhere to follow
|
|
5
|
+
# with StreamReader. What TaskProgress.handoff and TaskProgress.adopt write through. Under one
|
|
6
|
+
# key it keeps:
|
|
7
|
+
#
|
|
8
|
+
# key every batch, one entry each
|
|
9
|
+
# key:run the log id of the run that writes the tree now
|
|
10
|
+
# key:snapshot the tree squashed into one batch, and the id of the entry it is as of -
|
|
11
|
+
# where a reader starts, so the stream before it can be trimmed away
|
|
12
|
+
# key:beat there while the run is alive, and gone a few seconds after it is not
|
|
13
|
+
# key:control cancel requests, the other way - see Mount#follow_stream
|
|
14
|
+
#
|
|
15
|
+
# **One run at a time.** Each writer is a run, named by its log id, and claims `key:run` with its
|
|
16
|
+
# first batch. A run that a later one has taken over from - a job retried while the first
|
|
17
|
+
# attempt still limps on, two workers that adopted at once - may still append, and readers
|
|
18
|
+
# ignore what it appends; but every snapshot, trim, beat and expiry is written only while
|
|
19
|
+
# `key:run` still names the run that writes it, checked and written in one step. So a run that
|
|
20
|
+
# was replaced can never take the tree back.
|
|
21
|
+
#
|
|
22
|
+
# A snapshot is taken at the first batch, every `snapshot_every` seconds after, and when the
|
|
23
|
+
# root finishes, and each one trims the stream back to the one before it. Every key expires
|
|
24
|
+
# `retain` seconds after the last snapshot - a run that dies leaves nothing behind for good.
|
|
25
|
+
#
|
|
26
|
+
# With `heartbeat`, every so many seconds it marks itself alive and takes the cancel requests that
|
|
27
|
+
# came for it, on a thread of its own - from the first batch until the root finishes or it is
|
|
28
|
+
# closed. A request names the run it is for; a run also takes those for the run it took over
|
|
29
|
+
# from (`took_over`), so a cancel sent while a job still waited in the queue reaches whoever
|
|
30
|
+
# takes it up.
|
|
31
|
+
#
|
|
32
|
+
# A write that fails - Redis went away for a moment - is not lost: the next batch goes out as
|
|
33
|
+
# the whole tree, squashed, and readers start over from it
|
|
34
|
+
class StreamOutput
|
|
35
|
+
# The key it writes under
|
|
36
|
+
attr_reader key: String
|
|
37
|
+
|
|
38
|
+
@log: Log?
|
|
39
|
+
|
|
40
|
+
@replaced: bool
|
|
41
|
+
|
|
42
|
+
@covered: Integer
|
|
43
|
+
|
|
44
|
+
@broken: bool
|
|
45
|
+
|
|
46
|
+
@stop: Thread::Queue
|
|
47
|
+
|
|
48
|
+
@beating: Thread?
|
|
49
|
+
|
|
50
|
+
@snapped_at: Float
|
|
51
|
+
|
|
52
|
+
@snapshot_id: String?
|
|
53
|
+
|
|
54
|
+
@state: State
|
|
55
|
+
|
|
56
|
+
@run: String?
|
|
57
|
+
|
|
58
|
+
@took_over: String?
|
|
59
|
+
|
|
60
|
+
@retain: Integer | Float
|
|
61
|
+
|
|
62
|
+
@snapshot_every: Integer | Float
|
|
63
|
+
|
|
64
|
+
@heartbeat: (Integer | Float)?
|
|
65
|
+
|
|
66
|
+
@stream: untyped
|
|
67
|
+
|
|
68
|
+
# The key of the squashed tree a reader starts from
|
|
69
|
+
# : (String) -> String
|
|
70
|
+
def self.snapshot_key: (String) -> String
|
|
71
|
+
|
|
72
|
+
# The key that is there while the writer is alive
|
|
73
|
+
# : (String) -> String
|
|
74
|
+
def self.beat_key: (String) -> String
|
|
75
|
+
|
|
76
|
+
# The key cancel requests are appended to
|
|
77
|
+
# : (String) -> String
|
|
78
|
+
def self.control_key: (String) -> String
|
|
79
|
+
|
|
80
|
+
# Whether a cancel request is for one of these runs. A request names the run it was sent to -
|
|
81
|
+
# the one its sender saw writing the tree; one from a parent's control pipe names none
|
|
82
|
+
# : (String, *String?) -> bool
|
|
83
|
+
def self.for_run?: (String, *String?) -> bool
|
|
84
|
+
|
|
85
|
+
# The key that names the run writing the tree now
|
|
86
|
+
# : (String) -> String
|
|
87
|
+
def self.run_key: (String) -> String
|
|
88
|
+
|
|
89
|
+
# A key for a new tree. The braces make it a Redis Cluster hash tag, so all five keys of a
|
|
90
|
+
# tree land on the same node
|
|
91
|
+
# : (String) -> String
|
|
92
|
+
def self.key_for: (String) -> String
|
|
93
|
+
|
|
94
|
+
# Writes to `stream` under `key`. `took_over` is the run it takes the tree over from
|
|
95
|
+
# : (untyped, String, ?heartbeat: (Integer | Float)?, ?snapshot_every: Integer | Float, ?retain: Integer | Float, ?took_over: String?) -> void
|
|
96
|
+
def initialize: (untyped, String, ?heartbeat: (Integer | Float)?, ?snapshot_every: Integer | Float, ?retain: Integer | Float, ?took_over: String?) -> void
|
|
97
|
+
|
|
98
|
+
# Appends the batch, and takes a snapshot when one is due
|
|
99
|
+
# : (Batch, Log) -> void
|
|
100
|
+
def call: (Batch, Log) -> void
|
|
101
|
+
|
|
102
|
+
# Stops beating, and taking cancel requests - the work is over, one way or another. If the
|
|
103
|
+
# last write failed, the tree goes out whole once more, so readers do not stop short of the end
|
|
104
|
+
# : () -> void
|
|
105
|
+
def close: () -> void
|
|
106
|
+
|
|
107
|
+
# Whether a later run has taken the tree over, so this one writes no more
|
|
108
|
+
# : () -> bool
|
|
109
|
+
def replaced?: () -> bool
|
|
110
|
+
|
|
111
|
+
private
|
|
112
|
+
|
|
113
|
+
# : () -> Float
|
|
114
|
+
def now: () -> Float
|
|
115
|
+
|
|
116
|
+
# : () -> void
|
|
117
|
+
def stop: () -> void
|
|
118
|
+
|
|
119
|
+
# : (String) -> [String, String]
|
|
120
|
+
def guard: (String) -> [ String, String ]
|
|
121
|
+
|
|
122
|
+
# The run starts: from now on the tree is this run's to snapshot, trim and beat for
|
|
123
|
+
# : (String) -> String
|
|
124
|
+
def claim: (String) -> String
|
|
125
|
+
|
|
126
|
+
# The whole tree as one batch, in place of what failed to go out
|
|
127
|
+
# : (Log) -> Batch
|
|
128
|
+
def resync: (Log) -> Batch
|
|
129
|
+
|
|
130
|
+
# : (String, Batch, String) -> void
|
|
131
|
+
def snapshot: (String, Batch, String) -> void
|
|
132
|
+
|
|
133
|
+
# : (String) -> void
|
|
134
|
+
def finish: (String) -> void
|
|
135
|
+
|
|
136
|
+
# : () -> void
|
|
137
|
+
def replaced: () -> void
|
|
138
|
+
|
|
139
|
+
# : (Log) -> void
|
|
140
|
+
def beat: (Log) -> void
|
|
141
|
+
end
|
|
142
|
+
end
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
# Generated from lib/task_progress/stream_reader.rb with RBS::Inline
|
|
2
|
+
|
|
3
|
+
class TaskProgress
|
|
4
|
+
# Reads a tree a StreamOutput writes: the snapshot first, as one batch with `prev` 0, then
|
|
5
|
+
# every batch after it as it comes. Each read picks up where the last one left off.
|
|
6
|
+
#
|
|
7
|
+
# It hands on what it reads, and judges none of it - Mount does that, see Mount#follow_stream.
|
|
8
|
+
# After a gap, `resync!` makes the next read start over from the latest snapshot
|
|
9
|
+
class StreamReader
|
|
10
|
+
# The key it reads
|
|
11
|
+
attr_reader key: String
|
|
12
|
+
|
|
13
|
+
@stream: untyped
|
|
14
|
+
|
|
15
|
+
@after: String?
|
|
16
|
+
|
|
17
|
+
@started: bool
|
|
18
|
+
|
|
19
|
+
# Reads `stream` at `key`
|
|
20
|
+
# : (untyped, String) -> void
|
|
21
|
+
def initialize: (untyped, String) -> void
|
|
22
|
+
|
|
23
|
+
# The batches since the last read - starting from the snapshot. With a `timeout`, waits up
|
|
24
|
+
# to that many seconds for one when there are none yet
|
|
25
|
+
# : (?timeout: (Integer | Float)?) -> Array[Batch]
|
|
26
|
+
def read: (?timeout: (Integer | Float)?) -> Array[Batch]
|
|
27
|
+
|
|
28
|
+
# The next read starts over, from the latest snapshot
|
|
29
|
+
# : () -> void
|
|
30
|
+
def resync!: () -> void
|
|
31
|
+
|
|
32
|
+
# The tree as of the latest snapshot, as one batch - or nil when nothing was ever written
|
|
33
|
+
# here, or it has expired
|
|
34
|
+
# : () -> Batch?
|
|
35
|
+
def snapshot: () -> Batch?
|
|
36
|
+
|
|
37
|
+
# Whether the writer said lately that it is alive
|
|
38
|
+
# : () -> bool
|
|
39
|
+
def alive?: () -> bool
|
|
40
|
+
|
|
41
|
+
private
|
|
42
|
+
|
|
43
|
+
# : () -> [String, Batch]?
|
|
44
|
+
def snapshot_entry: () -> [ String, Batch ]?
|
|
45
|
+
end
|
|
46
|
+
end
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# Generated from lib/task_progress/summary_output.rb with RBS::Inline
|
|
2
|
+
|
|
3
|
+
class TaskProgress
|
|
4
|
+
# The tree boiled down to the root's percent and the latest thing said, for a progress
|
|
5
|
+
# column in a database or a single status line. A batch that changes neither is not passed on
|
|
6
|
+
class SummaryOutput
|
|
7
|
+
@block: ^(percent: Integer, message: String?) -> void
|
|
8
|
+
|
|
9
|
+
@percent: Integer
|
|
10
|
+
|
|
11
|
+
# Calls the block with `percent:` and `message:` whenever either changes
|
|
12
|
+
# : () { (percent: Integer, message: String?) -> void } -> void
|
|
13
|
+
def initialize: () { (percent: Integer, message: String?) -> void } -> void
|
|
14
|
+
|
|
15
|
+
# Takes one batch, see Log#add_output
|
|
16
|
+
# : (Batch, Log) -> void
|
|
17
|
+
def call: (Batch, Log) -> void
|
|
18
|
+
end
|
|
19
|
+
end
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
# Generated from lib/task_progress/terminal_output.rb with RBS::Inline
|
|
2
|
+
|
|
3
|
+
class TaskProgress
|
|
4
|
+
# Redraws the tree in place on a terminal, from a ticker thread of its own so spinners turn
|
|
5
|
+
# while nothing reports. A finished subtree folds into its one line, and when the tree is
|
|
6
|
+
# taller than the screen the finished lines go first, then the ones not started. When the
|
|
7
|
+
# root finishes the tree is drawn one last time and left on screen - or erased, with `erase: true`.
|
|
8
|
+
# A line a task `puts` goes above the tree and stays there
|
|
9
|
+
#
|
|
10
|
+
# With `interval: nil` there is no thread and every batch redraws, which is what tests want
|
|
11
|
+
class TerminalOutput
|
|
12
|
+
SPINNER: Array[String]
|
|
13
|
+
|
|
14
|
+
BAR: Integer
|
|
15
|
+
|
|
16
|
+
DEFAULT_SIZE: [ Integer, Integer ]
|
|
17
|
+
|
|
18
|
+
@dirty: bool
|
|
19
|
+
|
|
20
|
+
@ticker: Thread?
|
|
21
|
+
|
|
22
|
+
@closed: bool
|
|
23
|
+
|
|
24
|
+
@log: Log?
|
|
25
|
+
|
|
26
|
+
@frame: Integer
|
|
27
|
+
|
|
28
|
+
@drawn: Integer
|
|
29
|
+
|
|
30
|
+
@lock: Thread::Mutex
|
|
31
|
+
|
|
32
|
+
@clock: ^() -> Float
|
|
33
|
+
|
|
34
|
+
@erase: bool
|
|
35
|
+
|
|
36
|
+
@interval: (Integer | Float)?
|
|
37
|
+
|
|
38
|
+
@io: IO | StringIO
|
|
39
|
+
|
|
40
|
+
# Draws on `io` every `interval` seconds - or on every batch, with `interval: nil`. With `erase: true` the tree is cleared once the root finishes
|
|
41
|
+
# : (?IO | StringIO, ?interval: (Integer | Float)?, ?erase: bool, ?clock: ^() -> Float) -> void
|
|
42
|
+
def initialize: (?IO | StringIO, ?interval: (Integer | Float)?, ?erase: bool, ?clock: ^() -> Float) -> void
|
|
43
|
+
|
|
44
|
+
# Takes one batch, see Log#add_output
|
|
45
|
+
# : (Batch, Log) -> void
|
|
46
|
+
def call: (Batch, Log) -> void
|
|
47
|
+
|
|
48
|
+
# Draws the tree one last time and stops the ticker
|
|
49
|
+
# : () -> void
|
|
50
|
+
def close: () -> void
|
|
51
|
+
|
|
52
|
+
# The lines for a snapshot, without the terminal control around them
|
|
53
|
+
# : (State, ?width: Integer, ?height: Integer) -> Array[String]
|
|
54
|
+
def lines: (State, ?width: Integer, ?height: Integer) -> Array[String]
|
|
55
|
+
|
|
56
|
+
private
|
|
57
|
+
|
|
58
|
+
# : () -> void
|
|
59
|
+
def tick: () -> void
|
|
60
|
+
|
|
61
|
+
# : () -> void
|
|
62
|
+
def finish: () -> void
|
|
63
|
+
|
|
64
|
+
# : () -> void
|
|
65
|
+
def draw: () -> void
|
|
66
|
+
|
|
67
|
+
# : (Array[String]) -> void
|
|
68
|
+
def print_above: (Array[String]) -> void
|
|
69
|
+
|
|
70
|
+
# : () -> void
|
|
71
|
+
def clear: () -> void
|
|
72
|
+
|
|
73
|
+
# : () -> String
|
|
74
|
+
def up: () -> String
|
|
75
|
+
|
|
76
|
+
# Under a finished node only what failed shows, so a failure leaves the way down to it
|
|
77
|
+
# : (State, path) -> bool
|
|
78
|
+
def hidden?: (State, path) -> bool
|
|
79
|
+
|
|
80
|
+
# What fits in `room` lines - the last of them saying how many more there are
|
|
81
|
+
# : (Array[[path?, Node | Fold | Integer]], Integer) -> Array[[path?, Node | Fold | Integer]]
|
|
82
|
+
def trim: (Array[[ path?, Node | Fold | Integer ]], Integer) -> Array[[ path?, Node | Fold | Integer ]]
|
|
83
|
+
|
|
84
|
+
# : (State, path?, Node | Fold | Integer, Integer) -> String
|
|
85
|
+
def line: (State, path?, Node | Fold | Integer, Integer) -> String
|
|
86
|
+
|
|
87
|
+
# : (Node) -> String
|
|
88
|
+
def icon: (Node) -> String
|
|
89
|
+
|
|
90
|
+
# : (State, path, Node) -> String?
|
|
91
|
+
def measure: (State, path, Node) -> String?
|
|
92
|
+
|
|
93
|
+
# Diagonal stripes that crawl one cell along every frame - for work with no measure
|
|
94
|
+
# : () -> String
|
|
95
|
+
def stripes: () -> String
|
|
96
|
+
|
|
97
|
+
# : (Integer) -> String
|
|
98
|
+
def bar: (Integer) -> String
|
|
99
|
+
|
|
100
|
+
# The rate over the whole run so far, and the time left at that rate. Steadier than a
|
|
101
|
+
# rate over the last few ticks, which jumps about with every pause in the work
|
|
102
|
+
# : (Node) -> String?
|
|
103
|
+
def pace: (Node) -> String?
|
|
104
|
+
|
|
105
|
+
# : (Node) -> String?
|
|
106
|
+
def elapsed: (Node) -> String?
|
|
107
|
+
|
|
108
|
+
# : () -> Integer
|
|
109
|
+
def columns: () -> Integer
|
|
110
|
+
|
|
111
|
+
# : () -> Integer
|
|
112
|
+
def rows: () -> Integer
|
|
113
|
+
|
|
114
|
+
# : () -> [Integer, Integer]
|
|
115
|
+
def size: () -> [ Integer, Integer ]
|
|
116
|
+
end
|
|
117
|
+
end
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# Generated from lib/task_progress/token.rb with RBS::Inline
|
|
2
|
+
|
|
3
|
+
class TaskProgress
|
|
4
|
+
# The right to see a tree and to ask for its tasks to be canceled, signed, and good for a while.
|
|
5
|
+
# The server issues one when it hands out a page - after it has decided the page may show that
|
|
6
|
+
# tree - and checks it when the page comes back for the tree or with a cancellation. So neither
|
|
7
|
+
# can be forged without the secret, or used after the token expires, and the tree's own key never
|
|
8
|
+
# has to reach a page. Which tasks may be canceled is for `cancelable` to say.
|
|
9
|
+
#
|
|
10
|
+
# tokens = TaskProgress::Token.new(ENV.fetch("TASK_PROGRESS_SECRET"))
|
|
11
|
+
# token = tokens.issue(import.key, expires_in: 3600) # into the page
|
|
12
|
+
# tokens.verify(token) # => import.key, when the page comes back
|
|
13
|
+
class Token
|
|
14
|
+
@secret: String
|
|
15
|
+
|
|
16
|
+
@clock: ^() -> Integer
|
|
17
|
+
|
|
18
|
+
# Signs with `secret`, which must be at least 32 bytes
|
|
19
|
+
# : (String, ?clock: ^() -> Integer) -> void
|
|
20
|
+
def initialize: (String, ?clock: ^() -> Integer) -> void
|
|
21
|
+
|
|
22
|
+
# A token for `subject` - a log id, a run, whatever the tokens are checked against - good for `expires_in` seconds
|
|
23
|
+
# : (String, ?expires_in: Integer) -> String
|
|
24
|
+
def issue: (String, ?expires_in: Integer) -> String
|
|
25
|
+
|
|
26
|
+
# What the token was issued for, or nil if it is forged, mangled or expired
|
|
27
|
+
# : (String?) -> String?
|
|
28
|
+
def verify: (String?) -> String?
|
|
29
|
+
|
|
30
|
+
private
|
|
31
|
+
|
|
32
|
+
# : (String) -> String
|
|
33
|
+
def sign: (String) -> String
|
|
34
|
+
|
|
35
|
+
# : (String) -> String
|
|
36
|
+
def encode: (String) -> String
|
|
37
|
+
|
|
38
|
+
# : (String) -> String
|
|
39
|
+
def decode: (String) -> String
|
|
40
|
+
end
|
|
41
|
+
end
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# Generated from lib/task_progress/tracked_io.rb with RBS::Inline
|
|
2
|
+
|
|
3
|
+
class TaskProgress
|
|
4
|
+
# A ProgressiveIO that advances a task by how far it has been read. Readers seek (ZipKit reads
|
|
5
|
+
# the end of an archive first), so only the furthest position counts. The task moves in steps,
|
|
6
|
+
# a thousandth of the total by default - a loop of small reads over a big file would otherwise
|
|
7
|
+
# commit a batch per read, and every output and browser would get each of them
|
|
8
|
+
class TrackedIO < ::ProgressiveIO
|
|
9
|
+
STEP_WITHOUT_TOTAL: Integer
|
|
10
|
+
|
|
11
|
+
@task: TaskProgress
|
|
12
|
+
|
|
13
|
+
@total: Integer?
|
|
14
|
+
|
|
15
|
+
@step: Integer
|
|
16
|
+
|
|
17
|
+
@furthest: Integer
|
|
18
|
+
|
|
19
|
+
@reported: Integer
|
|
20
|
+
|
|
21
|
+
# Advances `task` as `io` is read, in steps of `step` bytes - see TaskProgress#track_io, which makes one
|
|
22
|
+
# : (IO, TaskProgress, ?total: Integer?, ?step: Integer?) -> void
|
|
23
|
+
def initialize: (IO, TaskProgress, ?total: Integer?, ?step: Integer?) -> void
|
|
24
|
+
|
|
25
|
+
# ProgressiveIO lets these through unreported, and IO.copy_stream reads anything that is not
|
|
26
|
+
# an IO with readpartial
|
|
27
|
+
# Reads the way IO#readpartial does, and reports how far it got
|
|
28
|
+
# : (*untyped, **untyped) -> untyped
|
|
29
|
+
def readpartial: (*untyped, **untyped) -> untyped
|
|
30
|
+
|
|
31
|
+
# Reads the way IO#read_nonblock does, and reports how far it got
|
|
32
|
+
# : (*untyped, **untyped) -> untyped
|
|
33
|
+
def read_nonblock: (*untyped, **untyped) -> untyped
|
|
34
|
+
|
|
35
|
+
# Reads the way IO#sysread does, and reports how far it got
|
|
36
|
+
# : (*untyped, **untyped) -> untyped
|
|
37
|
+
def sysread: (*untyped, **untyped) -> untyped
|
|
38
|
+
|
|
39
|
+
# Reports what was read since the last step
|
|
40
|
+
# : () -> void
|
|
41
|
+
def flush_progress: () -> void
|
|
42
|
+
|
|
43
|
+
private
|
|
44
|
+
|
|
45
|
+
# : (Integer) -> void
|
|
46
|
+
def moved_to: (Integer) -> void
|
|
47
|
+
end
|
|
48
|
+
end
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# Generated from lib/task_progress/transaction.rb with RBS::Inline
|
|
2
|
+
|
|
3
|
+
class TaskProgress
|
|
4
|
+
# What a block given to Log#transaction adds its events through. Each one is applied to the
|
|
5
|
+
# tree as it comes, and journaled, so the whole transaction can be undone if the block raises
|
|
6
|
+
class Transaction
|
|
7
|
+
# What it has added so far, in order
|
|
8
|
+
attr_reader events: Array[Event]
|
|
9
|
+
|
|
10
|
+
# What it changed, to undo it by
|
|
11
|
+
attr_reader journal: Journal
|
|
12
|
+
|
|
13
|
+
# The time it opened - every event in it happens then
|
|
14
|
+
attr_reader at: Float
|
|
15
|
+
|
|
16
|
+
# The tree as the transaction has it so far
|
|
17
|
+
attr_reader state: State
|
|
18
|
+
|
|
19
|
+
@random: Random
|
|
20
|
+
|
|
21
|
+
# For changing `state` at `at`, drawing task ids from `random` - see Log#transaction, which opens one
|
|
22
|
+
# : (State, Float, Random) -> void
|
|
23
|
+
def initialize: (State, Float, Random) -> void
|
|
24
|
+
|
|
25
|
+
# Applies the event, and adds it to the batch
|
|
26
|
+
# : (Event) -> self
|
|
27
|
+
def <<: (Event) -> self
|
|
28
|
+
|
|
29
|
+
# A path for a new child of `path` - see State#new_child
|
|
30
|
+
# : (path) -> path
|
|
31
|
+
def new_child: (path) -> path
|
|
32
|
+
end
|
|
33
|
+
end
|