cuboid 0.4 → 0.5

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 51212d2d1adc9ffb33f4838212d9bbddcb88466a12e8e1b5522a2baff6b233f0
4
- data.tar.gz: e4721af5a566266882fea5320129f58d01c667a03956d79300a3b3a3b652edc1
3
+ metadata.gz: 28dd253a7fd5dc33e5f8d62a9ae2fae905a46a786bb21d57cb8c47f9792ba821
4
+ data.tar.gz: 319fcbba6a3a5ed6b41eec3dbcde53c7e4f7d43c658df4657e660339e2198933
5
5
  SHA512:
6
- metadata.gz: c2cec6fd1a808c0fc1473a61a7f93a0e3ed7a974562df6c44b55e4e450461cef29320d82a252de344eb1d21ceec94f6e4e8bd7bfd7f7301ccb05f15561c3255e
7
- data.tar.gz: c7f15a7207fa170a0874c9931a75533bb6ba37dda9a0b2a0b2946a019ea90f16258b5051ab523fbc5ca59795c0f948ff18fac1434c55ca7ae7b860198576b94e
6
+ metadata.gz: 859207bc319b54b3eaff619dcdc1aa770e7136fe30cea8dcc86556595a204711190c0a463398038ee30a5ce8a9be25eea3637fc79676daa150b045f8b4ab880e
7
+ data.tar.gz: 64c5901b28b942d2771dca8ec22af4ad893a8d11c53acf1d094a7c65882c9199105d18b5bbc8872b094a1447896d07897b1bf1db8b85e808a569a2ae92e19cbc
@@ -5,9 +5,8 @@ module Rest
5
5
  class Server
6
6
 
7
7
  # Sinatra-coupled supplement to `Cuboid::Server::InstanceHelpers` —
8
- # the methods that read `env`, call `handle_error` (a Sinatra helper
9
- # defined on `Rest::Server`), or prune `session` entries belonging to
10
- # scheduler-removed instances. Everything that doesn't need Sinatra
8
+ # the methods that read `env` or call `handle_error` (a Sinatra helper
9
+ # defined on `Rest::Server`). Everything that doesn't need Sinatra
11
10
  # stays on the shared module above.
12
11
  module InstanceHelpers
13
12
 
@@ -19,24 +18,8 @@ module InstanceHelpers
19
18
  super
20
19
  end
21
20
 
22
- # Adds Sinatra-session cleanup for IDs the scheduler has dropped.
23
- # The shared `update_from_scheduler` already removes them from the
24
- # instance map; this override prunes the matching session keys so a
25
- # second request from the same browser doesn't try to reach a dead
26
- # instance.
27
- def update_from_scheduler
28
- return if !scheduler
29
-
30
- pruned = scheduler.failed.keys | scheduler.completed.keys
31
- super
32
- pruned.each { |id| session.delete id }
33
- end
34
-
35
21
  def instance_for( id, &block )
36
- cleanup = proc do
37
- instances.delete( id ).close
38
- session.delete id
39
- end
22
+ cleanup = proc { instances.delete( id ).close }
40
23
 
41
24
  handle_error cleanup do
42
25
  block.call instances[id]
@@ -36,20 +36,19 @@ module Instances
36
36
  app.get '/instances/:instance' do
37
37
  ensure_instance!
38
38
 
39
- session[params[:instance]] ||= {
40
- seen_errors: 0,
41
- }
39
+ # The Sinatra session is per-cookie, but its id isn't
40
+ # exposed; lazy-init a per-client UUID and pass it as
41
+ # the RPC `session:` token so the engine tracks the
42
+ # error-line offset server-side.
43
+ require 'securerandom' unless defined?( SecureRandom )
44
+ session[:rpc_session_id] ||= SecureRandom.uuid
42
45
 
43
46
  data = instance_for( params[:instance] ) do |instance|
44
47
  instance.progress(
45
- with: [
46
- errors: session[params[:instance]][:seen_errors],
47
- ]
48
+ session: "#{session[:rpc_session_id]}:#{params[:instance]}"
48
49
  )
49
50
  end
50
51
 
51
- session[params[:instance]][:seen_errors] += data[:errors].size
52
-
53
52
  json data
54
53
  end
55
54
 
@@ -114,8 +113,6 @@ module Instances
114
113
 
115
114
  instances.delete( id ).close
116
115
 
117
- session.delete params[:instance]
118
-
119
116
  json nil
120
117
  end
121
118
 
@@ -152,51 +152,38 @@ class Instance
152
152
  # In addition, ask to **not** be served data you already have, like
153
153
  # error messages.
154
154
  #
155
- # To be kept completely up to date on the progress of a scan (i.e. receive
156
- # new issues and error messages asap) in an efficient manner, you will need
157
- # to keep track of the error messages you already have and explicitly tell
158
- # the method to not send the same data back to you on subsequent calls.
155
+ # Pass a `session:` token (any caller-chosen string) and the
156
+ # server returns only error lines past the previous offset
157
+ # under that token. Reuse the same token across polls for
158
+ # the same logical view; pick a fresh one to start fresh.
159
159
  #
160
- # ## Retrieving errors (`:errors` option) without duplicate data
161
- #
162
- # This is done by telling the method how many error messages you already
163
- # have and you will be served the errors from the error-log that are past
164
- # that line.
165
- # So, if you were to use a loop to get fresh progress data it would look
166
- # like so:
167
- #
168
- # error_cnt = 0
169
- # i = 0
160
+ # token = SecureRandom.uuid
170
161
  # while sleep 1
171
- # # Test method, triggers an error log...
172
- # instance.error_test "BOOM! #{i+=1}"
173
- #
174
- # # Only request errors we don't already have
175
- # errors = instance.progress( with: { errors: error_cnt } )[:errors]
176
- # error_cnt += errors.size
177
- #
178
- # # You will only see new errors
179
- # puts errors.join("\n")
162
+ # errors = instance.progress( session: token )[:errors]
163
+ # puts errors.join( "\n" )
180
164
  # end
181
165
  #
182
- # @param [Hash] options
183
- # Options about what progress data to retrieve and return.
184
- # @option options [Array<Symbol, Hash>] :with
185
- # Specify data to include:
186
- #
187
- # * :errors -- Errors and the line offset to use for {#errors}.
188
- # Pass as a hash, like: `{ errors: 10 }`
189
- # @option options [Array<Symbol, Hash>] :without
190
- # Specify data to exclude:
166
+ # Without `session`, callers must opt into errors via
167
+ # `with: [:errors]` and will receive the full set every poll.
191
168
  #
192
- # * :statistics -- Don't include runtime statistics.
169
+ # @param [Hash] options
170
+ # @option options [String, Symbol] :session
171
+ # Caller-chosen session token. When provided, the response
172
+ # carries only errors past the previously emitted offset.
173
+ # @option options [Array<Symbol>] :with
174
+ # Block names to include when no session is in use. Currently
175
+ # only `:errors` is delta-able.
176
+ # @option options [Array<Symbol>] :without
177
+ # Block names to exclude. One or more of `:statistics`,
178
+ # `:errors`. Takes precedence over `with:` and over the
179
+ # session-on-by-default blocks.
193
180
  #
194
181
  # @return [Hash]
195
182
  # * `statistics` -- General runtime statistics (merged when part of Grid)
196
183
  # (enabled by default)
197
184
  # * `status` -- {#status}
198
185
  # * `busy` -- {#busy?}
199
- # * `errors` -- {#errors} (disabled by default)
186
+ # * `errors` -- {#errors}
200
187
  def progress( options = {} )
201
188
  progress_handler( options.merge( as_hash: true ) )
202
189
  end
@@ -294,49 +281,50 @@ class Instance
294
281
  [Process.pid]
295
282
  end
296
283
 
297
- def self.parse_progress_opts( options, key )
298
- parsed = {}
299
- [options.delete( key ) || options.delete( key.to_s )].compact.each do |w|
300
- case w
301
- when Array
302
- w.compact.flatten.each do |q|
303
- case q
304
- when String, Symbol
305
- parsed[q.to_sym] = nil
306
-
307
- when Hash
308
- parsed.merge!( q.my_symbolize_keys )
309
- end
310
- end
311
-
312
- when String, Symbol
313
- parsed[w.to_sym] = nil
314
-
315
- when Hash
316
- parsed.merge!( w.my_symbolize_keys )
317
- end
318
- end
319
-
320
- parsed
284
+ def self.parse_block_names( raw )
285
+ return [] if raw.nil?
286
+ Array( raw ).flatten.compact.map(&:to_sym)
321
287
  end
322
288
 
323
289
  private
324
290
 
291
+ # Server-side state for `session:`-tracked progress polls.
292
+ # Keyed off a caller-supplied token so RPC clients don't have
293
+ # to re-transmit the error line offset on every poll.
294
+ def progress_sessions
295
+ @progress_sessions ||= {}
296
+ end
297
+
298
+ def progress_session_for( id )
299
+ progress_sessions[id] ||= { seen_errors: 0 }
300
+ end
301
+
325
302
  def progress_handler( options = {}, &block )
326
- with = self.class.parse_progress_opts( options, :with )
327
- without = self.class.parse_progress_opts( options, :without )
303
+ options = options.my_symbolize_keys
304
+
305
+ session_id = options.delete( :session )
306
+ session = progress_session_for( session_id ) if session_id
307
+
308
+ with = self.class.parse_block_names( options[:with] )
309
+ without = self.class.parse_block_names( options[:without] )
310
+
311
+ # Under a session, errors are on by default; without a
312
+ # session, callers opt in via `with: [:errors]`.
313
+ include_errors = !without.include?( :errors ) && (session || with.include?( :errors ))
328
314
 
329
- options = {
315
+ wrapper_options = {
330
316
  as_hash: options[:as_hash],
331
317
  statistics: !without.include?( :statistics )
332
318
  }
319
+ wrapper_options[:errors] = session ? session[:seen_errors] : 0 if include_errors
333
320
 
334
- if with[:errors]
335
- options[:errors] = with[:errors]
336
- end
337
-
338
- @application.progress( options ) do |data|
321
+ @application.progress( wrapper_options ) do |data|
339
322
  data[:busy] = busy?
323
+
324
+ if session && data[:errors]
325
+ session[:seen_errors] += data[:errors].size
326
+ end
327
+
340
328
  block.call( data )
341
329
  end
342
330
  end
data/lib/version CHANGED
@@ -1 +1 @@
1
- 0.4
1
+ 0.5
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: cuboid
3
3
  version: !ruby/object:Gem::Version
4
- version: '0.4'
4
+ version: '0.5'
5
5
  platform: ruby
6
6
  authors:
7
7
  - Tasos Laskos
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2026-05-06 00:00:00.000000000 Z
11
+ date: 2026-05-09 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: awesome_print