phlex-reactive 0.13.1 → 0.13.2

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: '049bef601e70264a857668de2ad7766f9b4edae020ded0844d2f5efacdc0607d'
4
- data.tar.gz: 1293bfd5f14ef03fa1889444c662846d4f1129155e1b9ea458b48529a7644b3b
3
+ metadata.gz: cb580ca60e8e64b7857e665f34e5311e35f66c8aa4348135c61286456b06649f
4
+ data.tar.gz: fb25bb13bd78f0923f6a7a3c5ea383101cca16eaf9b808607e074c49dbbaa583
5
5
  SHA512:
6
- metadata.gz: d3fe07e736d776bdcdc594ef9d4cb3e91aea4c0cf81d6bf9c0f703d5de744e2d60394b79c4bad02c32b98ac04fc99d3b1a6746c88ff8e377e94e259765ddbfec
7
- data.tar.gz: eb3c04c9cea640d1c6662e602a77b1b49e4e5e7204cb6c587916440c5695064a84219348127b7282ee83b9dd6e6fa57dced2ca9dd79fd7eba1cb1e33e56d6419
6
+ metadata.gz: 6d50f4794edea6b74bf4178df8d4a271e947e8bc3eef268d19ba8cb855c3207e87ee9c3bd345a22b3d9192f58510bb00860278c64a59c7f1e1b3478b72af62c0
7
+ data.tar.gz: '0233287800960c1886865f7df4fe2785237820d16e212b616e1971c879d0a97abd373f55588f2921aef4bd742d8cb053ffb2b560a81862517767dbd883556fac'
data/CHANGELOG.md CHANGED
@@ -441,6 +441,24 @@ adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
441
441
 
442
442
  ### Fixed
443
443
 
444
+ - **`reply.pending` kept its settle handle under
445
+ `enqueue_after_transaction_commit = true` (#254).** The handle was captured in
446
+ `Phlex::Reactive::Settles#serialize`, which reads a thread-local that lives
447
+ only for the duration of `reply.pending`'s enqueue block. Under Rails'
448
+ `ActiveJob::Base.enqueue_after_transaction_commit = true` (the 7.2+
449
+ recommended setting) `job.enqueue` is deferred to
450
+ `ActiveRecord.after_all_transactions_commit`, and the endpoint runs every
451
+ action inside a transaction — so `serialize` always ran AFTER the block had
452
+ exited. The job serialized with no `phlex_reactive_settle` key,
453
+ `reactive_settle` was a no-op, and the row sat shimmering until someone
454
+ reloaded. The handle is now captured when the job INSTANCE is created
455
+ (`perform_later` → `job_or_instantiate` → `new`), which is synchronous inside
456
+ the block whether or not the enqueue itself is deferred; `serialize` prefers
457
+ the captured handle and still falls back to the thread-local. Both the
458
+ `job:`/`args:` sugar (narrowing per record, so failures stay attributable) and
459
+ the block form are covered, and a retry re-enqueue keeps its handle. Jobs
460
+ enqueued outside any `reply.pending` still carry nothing.
461
+
444
462
  - **`rake release` bumps the pin in every tracked lockfile (#247, #253).** Since
445
463
  #246 the gem root's `Gemfile.lock` is committed alongside `docs/Gemfile.lock`,
446
464
  and both pin `phlex-reactive` by local path — so a version bump that left them
data/README.md CHANGED
@@ -2591,10 +2591,17 @@ bug). It emits:
2591
2591
 
2592
2592
  - **The handle rides ActiveJob metadata, not `perform`'s arity.** `reply.pending`
2593
2593
  installs it in a thread-local and runs your enqueue inside it;
2594
- `Settles#serialize` copies it into the job's metadata. So **every other caller
2595
- of the same job a nightly sweep, a webhook keeps working unchanged**, and
2596
- `reactive_settle` is simply a no-op there. That is load-bearing: these jobs
2597
- almost always have non-UI callers.
2594
+ `Settles#initialize` captures it onto the job instance and `#serialize` copies
2595
+ it into the job's metadata. So **every other caller of the same job a
2596
+ nightly sweep, a webhook keeps working unchanged**, and `reactive_settle` is
2597
+ simply a no-op there. That is load-bearing: these jobs almost always have
2598
+ non-UI callers.
2599
+ - **It works under `enqueue_after_transaction_commit = true`.** Rails defers that
2600
+ enqueue to `ActiveRecord.after_all_transactions_commit`, and the endpoint runs
2601
+ your action inside a transaction — so the enqueue (and its `serialize`) happens
2602
+ *after* the block has exited. The capture is therefore at job
2603
+ **instantiation**, which is synchronous inside the block either way. Nothing to
2604
+ configure.
2598
2605
  - **A job that raises still clears the pending state — when it can attribute
2599
2606
  it.** The `job:`/`args:` form enqueues one job per record and narrows each
2600
2607
  job's handle to *that record's* target, so a failure clears exactly its row
@@ -27,15 +27,19 @@ module Phlex
27
27
  # ## The handle rides ActiveJob metadata
28
28
  #
29
29
  # `reply.pending` installs a Pending::Handle in a thread-local and runs the
30
- # caller's enqueue inside it; #serialize below copies it into the job's
31
- # metadata, #deserialize restores it. So `perform`'s ARITY IS UNTOUCHED and
32
- # every OTHER caller of the same job a nightly sweep, a webhook — enqueues
33
- # it with no handle, in which case `reactive_settle` is a NO-OP that returns
34
- # nil. That is load-bearing: these jobs almost always have non-UI callers.
30
+ # caller's enqueue inside it; #initialize below captures it onto the job
31
+ # instance, #serialize copies it into the job's metadata, #deserialize
32
+ # restores it. So `perform`'s ARITY IS UNTOUCHED and every OTHER caller of
33
+ # the same job — a nightly sweep, a webhook builds it with no handle, in
34
+ # which case `reactive_settle` is a NO-OP that returns nil. That is
35
+ # load-bearing: these jobs almost always have non-UI callers.
35
36
  #
36
- # (`perform_now` does not round-trip through serialize/deserialize, so it
37
- # carries no handle eitherwhich is the correct reading: a synchronous
38
- # call has no pending UI waiting on it.)
37
+ # (A `perform_now` INSIDE the enqueue block does carry the handle, since the
38
+ # instance is built thereso it settles synchronously. That is the right
39
+ # reading: `reply.pending` already marked the targets, and something has to
40
+ # resolve the shimmer. The settle's durable message is replayed from
41
+ # since-id 0 when the client opens the subscription, so arriving before it
42
+ # exists is safe.)
39
43
  #
40
44
  # ## A rolled-back action
41
45
  #
@@ -57,12 +61,45 @@ module Phlex
57
61
  # shared namespace with every other gem in the app.
58
62
  SETTLE_METADATA_KEY = "phlex_reactive_settle"
59
63
 
60
- # Capture the in-flight settle handle at ENQUEUE time. This is the same
61
- # seam pgbus's own ActiveJob::CurrentAttributes integration uses, and it
62
- # is adapter-agnostic: it works under :async, :test and :inline as well as
63
- # a real backend, which is what app specs need.
64
+ # Capture the in-flight settle handle at INSTANTIATION (issue #254).
65
+ #
66
+ # `serialize` is the seam pgbus's own ActiveJob::CurrentAttributes
67
+ # integration uses, and it looked like the enqueue-time hook — but under
68
+ # Rails' `enqueue_after_transaction_commit = true` (the 7.2+ recommended
69
+ # setting) `job.enqueue` is deferred to
70
+ # ActiveRecord.after_all_transactions_commit, and the endpoint runs every
71
+ # action inside a transaction. So the deferral — and with it `serialize` —
72
+ # always fires AFTER reply.pending's `with_handle` block has exited, with
73
+ # an empty thread-local: no metadata key, a no-op `reactive_settle`, and a
74
+ # row left shimmering until someone reloads.
75
+ #
76
+ # `new` is the one moment guaranteed to be inside the block:
77
+ # `perform_later` → `job_or_instantiate` → `new` is synchronous, deferral
78
+ # or not. Pending#each_with_narrowed_handle narrows the thread-local per
79
+ # record BEFORE `perform_later`, so the `job:`/`args:` attribution
80
+ # contract is preserved.
81
+ def initialize(...)
82
+ super
83
+ @reactive_settle_handle ||= Phlex::Reactive::Pending.current_handle
84
+ end
85
+
86
+ # The same capture at ENQUEUE, for an instance built BEFORE the block and
87
+ # enqueued inside it (`job = MyJob.new(...)` … `reply.pending { job.enqueue }`).
88
+ # `enqueue` runs synchronously inside the block — it is the deferral it
89
+ # REGISTERS that runs later — so this is the last moment the thread-local
90
+ # is visible. `||=` never overwrites: a retry's `retry_job` re-enqueues a
91
+ # DESERIALIZED instance, which must keep the handle it came back with.
92
+ def enqueue(...)
93
+ @reactive_settle_handle ||= Phlex::Reactive::Pending.current_handle
94
+ super
95
+ end
96
+
97
+ # Prefer the captured handle; the thread-local fallback covers an instance
98
+ # serialized inside the block without going through either hook. A retry
99
+ # re-enqueue re-serializes the SAME instance, which keeps its handle — the
100
+ # right reading: the pending UI is still waiting on this work.
64
101
  def serialize
65
- handle = Phlex::Reactive::Pending.current_handle
102
+ handle = @reactive_settle_handle || Phlex::Reactive::Pending.current_handle
66
103
  return super unless handle
67
104
 
68
105
  super.merge(SETTLE_METADATA_KEY => handle.to_h_wire)
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Phlex
4
4
  module Reactive
5
- VERSION = "0.13.1"
5
+ VERSION = "0.13.2"
6
6
  end
7
7
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: phlex-reactive
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.13.1
4
+ version: 0.13.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Mikael Henriksson