1313
1414package io .reactivex .rxjava4 .core ;
1515
16+ import java .lang .ref .Cleaner ;
17+ import java .util .Objects ;
1618import java .util .concurrent .CompletionStage ;
19+ import java .util .concurrent .atomic .AtomicBoolean ;
20+ import java .util .function .Consumer ;
1721
1822import io .reactivex .rxjava4 .annotations .NonNull ;
1923import io .reactivex .rxjava4 .disposables .*;
24+ import io .reactivex .rxjava4 .plugins .RxJavaPlugins ;
2025
2126/**
2227 * Consist of a terminal stage and a disposable to be able to cancel a sequence.
2328 * @param <T> the return and element type of the various stages
24- * @param stage the embedded stage to work with
25- * @param disposable the way to cancel the stage concurrently
2629 * @since 4.0.0
2730 */
28- public record CompletionStageDisposable <T >( @ NonNull CompletionStage < T > stage , @ NonNull Disposable disposable ) {
31+ public final class CompletionStageDisposable <T > implements AutoCloseable {
2932
33+ // record classes can't have extra fields, why?
34+ // also I have to write out the constructor instead of declaring it in the record definition, FFS
35+
36+ static final Cleaner cleaner = Cleaner .create ();
37+
38+ static volatile Consumer <Cleaner .Cleanable > trackAllocations ;
39+
40+ static final class State extends AtomicBoolean implements Runnable {
41+
42+ /** */
43+ private static final long serialVersionUID = 262854674341831347L ;
44+
45+ Throwable allocationTrace ;
46+
47+ @ Override
48+ public void run () {
49+ if (!get ()) {
50+ RxJavaPlugins .onError (
51+ new IllegalStateException ("CompletionStageDisposable was not awaited or ignored explicitly" ,
52+ allocationTrace ));
53+ }
54+ }
55+
56+ }
57+
58+ final CompletionStage <T > stage ;
59+ final Disposable disposable ;
60+ final State state ;
61+ final Cleaner .Cleanable cleanable ;
62+
63+ /**
64+ * Construct an instance with parameters
65+ * @param stage the stage to be awaited
66+ * @param disposable the disposable to cancel asynchronously
67+ */
68+ public CompletionStageDisposable (@ NonNull CompletionStage <T > stage , @ NonNull Disposable disposable ) {
69+ Objects .requireNonNull (stage , "stage is null" );
70+ Objects .requireNonNull (disposable , "disposable is null" );
71+ this .stage = stage ;
72+ this .disposable = disposable ;
73+ this .state = new State ();
74+ this .cleanable = cleaner .register (this , state );
75+ if (trackAllocations != null ) {
76+ state .allocationTrace = new StackOverflowError ("CompletionStageDisposable::AllocationTrace" );
77+ trackAllocations .accept (this .cleanable );
78+ } else {
79+ state .allocationTrace = null ;
80+ }
81+ }
3082 /**
3183 * Await the completion of the current stage.
3284 */
3385 public void await () {
86+ state .lazySet (true );;
3487 Streamer .await (stage );
3588 }
3689
@@ -39,6 +92,56 @@ public void await() {
3992 * @param canceller the canceller link
4093 */
4194 public void await (DisposableContainer canceller ) {
95+ state .lazySet (true );;
4296 Streamer .await (stage , canceller );
4397 }
98+
99+ /**
100+ * Indicate this instance is deliberately not awaiting its stage.
101+ */
102+ public void ignore () {
103+ state .lazySet (true );;
104+ }
105+
106+ @ Override
107+ public void close () {
108+ try {
109+ state .lazySet (true );
110+ disposable .dispose ();
111+ } finally {
112+ cleanable .clean ();
113+ }
114+ }
115+
116+ /**
117+ * Set an allocator tracer callback to track where CompletionStageDisposables are leaking.
118+ * @param callback the callback to call when a new trace is being established
119+ */
120+ public static void setAllocationTrace (Consumer <Cleaner .Cleanable > callback ) {
121+ trackAllocations = callback ;
122+ }
123+
124+ /**
125+ * Returns the current allocation stacktrace capturing consumer.
126+ * @return the current allocation stacktrace capturing consumer.
127+ */
128+ public static Consumer <Cleaner .Cleanable > getAllocationTrace () {
129+ return trackAllocations ;
130+ }
131+
132+ /***
133+ * Returns the associated completion stage value.
134+ * @return the associated completion stage value.
135+ */
136+ public CompletionStage <T > stage () {
137+ return stage ;
138+ }
139+
140+ /**
141+ * Returns the associated disposable value.
142+ * @return the associated disposable value.
143+ */
144+ public Disposable disposable () {
145+ return disposable ;
146+ }
44147}
0 commit comments