<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
</head>
<body>
<p>It's an interesting problem. I wrote some test cases, and came to
this solution to allow large amounts of chained subscriptions,
while maintaining Subscriptions as immutable constructs that will
only unsubscribe exactly the right subscriptions when using older
references:</p>
<div style="background-color:#ffffff;padding:0px 0px 0px 2px;">
<div
style="color:#000000;background-color:#ffffff;font-family:"Consolas";font-size:11pt;white-space:pre;"><p
style="margin:0;"><span style="color:#000000;"> </span><span
style="color:#0000a0;font-weight:bold;">default</span><span
style="color:#000000;"> Subscription and(Subscription other) {</span></p><p
style="margin:0;"><span style="color:#000000;"> Objects.</span><span
style="color:#000000;font-style:italic;">requireNonNull</span><span
style="color:#000000;">(other, </span><span style="color:#2a00ff;">"other cannot be null"</span><span
style="color:#000000;">);</span></p><p style="margin:0;">
</p><p style="margin:0;"><span style="color:#000000;"> </span><span
style="color:#7f0055;font-weight:bold;">return</span><span
style="color:#000000;"> </span><span
style="color:#0000a0;font-weight:bold;">new</span><span
style="color:#000000;"> NodeSubscription(</span><span
style="color:#0000a0;font-weight:bold;">this</span><span
style="color:#000000;">, other);</span></p><p style="margin:0;"><span
style="color:#000000;"> }</span></p><p style="margin:0;">
</p><p style="margin:0;"><span style="color:#000000;"> </span><span
style="color:#3f7f5f;">// </span><span
style="color:#ff8040;font-weight:bold;">TODO</span><span
style="color:#3f7f5f;"> move to package to make it package private</span></p><p
style="margin:0;"><span style="color:#000000;"> </span><span
style="color:#0000a0;font-weight:bold;">static</span><span
style="color:#000000;"> </span><span
style="color:#0000a0;font-weight:bold;">class</span><span
style="color:#000000;"> NodeSubscription </span><span
style="color:#0000a0;font-weight:bold;">implements</span><span
style="color:#000000;"> Subscription {</span></p><p
style="margin:0;"><span style="color:#000000;"> </span><span
style="color:#0000a0;font-weight:bold;">private</span><span
style="color:#000000;"> </span><span
style="color:#0000a0;font-weight:bold;">final</span><span
style="color:#000000;"> Subscription </span><span
style="color:#0000c0;">s1</span><span style="color:#000000;">;</span></p><p
style="margin:0;"><span style="color:#000000;"> </span><span
style="color:#0000a0;font-weight:bold;">private</span><span
style="color:#000000;"> </span><span
style="color:#0000a0;font-weight:bold;">final</span><span
style="color:#000000;"> Subscription </span><span
style="color:#0000c0;">s2</span><span style="color:#000000;">;</span></p><p
style="margin:0;">
</p><p style="margin:0;"><span style="color:#000000;"> </span><span
style="color:#0000a0;font-weight:bold;">public</span><span
style="color:#000000;"> NodeSubscription(Subscription </span><span
style="text-decoration:underline;text-decoration-style:solid;text-decoration-color:#0066cc;color:#0066cc;">s1</span><span
style="color:#000000;">, Subscription s2) {</span></p><p
style="margin:0;"><span style="color:#000000;"> </span><span
style="color:#0000a0;font-weight:bold;">boolean</span><span
style="color:#000000;"> shouldSwap = s1 </span><span
style="color:#0000a0;font-weight:bold;">instanceof</span><span
style="color:#000000;"> NodeSubscription; </span><span
style="color:#3f7f5f;">// attempt to avoid growing stack as much as possible</span></p><p
style="margin:0;">
</p><p style="margin:0;"><span style="color:#000000;"> </span><span
style="color:#0000a0;font-weight:bold;">this</span><span
style="color:#000000;">.</span><span style="color:#0000c0;">s1</span><span
style="color:#000000;"> = shouldSwap ? s2 : s1;</span></p><p
style="margin:0;"><span style="color:#000000;"> </span><span
style="color:#0000a0;font-weight:bold;">this</span><span
style="color:#000000;">.</span><span style="color:#0000c0;">s2</span><span
style="color:#000000;"> = shouldSwap ? s1 : s2;</span></p><p
style="margin:0;"><span style="color:#000000;"> }</span></p><p
style="margin:0;">
</p><p style="margin:0;"><span style="color:#000000;"> </span><span
style="color:#646464;">@Override</span></p><p style="margin:0;"><span
style="color:#000000;"> </span><span
style="color:#0000a0;font-weight:bold;">public</span><span
style="color:#000000;"> </span><span
style="color:#0000a0;font-weight:bold;">void</span><span
style="color:#000000;"> unsubscribe() {</span></p><p
style="margin:0;"><span style="color:#000000;"> List<Subscription> stack = </span><span
style="color:#0000a0;font-weight:bold;">new</span><span
style="color:#000000;"> ArrayList<>();</span></p><p
style="margin:0;">
</p><p style="margin:0;"><span style="color:#000000;"> stack.add(</span><span
style="color:#0000a0;font-weight:bold;">this</span><span
style="color:#000000;">);</span></p><p style="margin:0;">
</p><p style="margin:0;"><span style="color:#000000;"> </span><span
style="color:#0000a0;font-weight:bold;">while</span><span
style="color:#000000;">(!stack.isEmpty()) {</span></p><p
style="margin:0;"><span style="color:#000000;"> Subscription s = stack.removeLast();</span></p><p
style="margin:0;">
</p><p style="margin:0;"><span style="color:#000000;"> </span><span
style="color:#0000a0;font-weight:bold;">if</span><span
style="color:#000000;">(s </span><span
style="color:#0000a0;font-weight:bold;">instanceof</span><span
style="color:#000000;"> NodeSubscription n) {</span></p><p
style="margin:0;"><span style="color:#000000;"> stack.add(n.</span><span
style="color:#0000c0;">s1</span><span style="color:#000000;">);</span></p><p
style="margin:0;"><span style="color:#000000;"> stack.add(n.</span><span
style="color:#0000c0;">s2</span><span style="color:#000000;">);</span></p><p
style="margin:0;"><span style="color:#000000;"> }</span></p><p
style="margin:0;"><span style="color:#000000;"> </span><span
style="color:#0000a0;font-weight:bold;">else</span><span
style="color:#000000;"> {</span></p><p style="margin:0;"><span
style="color:#000000;"> s.unsubscribe();</span></p><p
style="margin:0;"><span style="color:#000000;"> }</span></p><p
style="margin:0;"><span style="color:#000000;"> }</span></p><p
style="margin:0;"><span style="color:#000000;"> }</span></p><p
style="margin:0;"><span style="color:#000000;"> }</span></p></div>
</div>
<p></p>
<p>Note: as prescribed by the specification, `unsubscribe` is
idempotent.<br>
</p>
<p>--John<br>
</p>
<p><br>
</p>
<div class="moz-cite-prefix">On 13/08/2025 22:11, John Hendrikx
wrote:<br>
</div>
<blockquote type="cite"
cite="mid:cb29a0fd-8e4a-4009-b2c3-9944ab76f57c@gmail.com">
<pre class="moz-quote-pre" wrap="">You're right, this should preferably not happen. The implementation is
simple, but it does limit how many subscriptions you can chain.
The `combine` variant does not have the same issue.
I can rewrite it to avoid recursion, but it needs to be done very
carefully as the subscriptions chained with `and` basically form a
graph, and unsubscribing an older reference should not unsubscribe more
than it would have done before calling `and`.
--John
On 13/08/2025 12:06, Johan Vos wrote:
</pre>
<blockquote type="cite">
<pre class="moz-quote-pre" wrap="">Hi,
The current implementation of Subscription.unsubscribe() uses
recursion to unsubscribe the chain of subscriptions. This can lead to
a StackOverflowError in case there are many chained subscriptions.
Running the following code demonstrates this:
```
void testSubs() {
SimpleStringProperty prop = new SimpleStringProperty("simpel");
Subscription s = prop.subscribe(() -> {});
for (int i = 0; i < 100000; i++) {
Subscription t = prop.subscribe(() -> {});
s = s.and(t);
}
s.unsubscribe();
}
```
This results in
```
java.lang.StackOverflowError
at
<a class="moz-txt-link-abbreviated" href="mailto:javafx.base@26-ea/javafx.util.Subscription.lambda$and$0">javafx.base@26-ea/javafx.util.Subscription.lambda$and$0</a>(Subscription.java:103)
at
<a class="moz-txt-link-abbreviated" href="mailto:javafx.base@26-ea/javafx.util.Subscription.lambda$and$0">javafx.base@26-ea/javafx.util.Subscription.lambda$and$0</a>(Subscription.java:103)
at
<a class="moz-txt-link-abbreviated" href="mailto:javafx.base@26-ea/javafx.util.Subscription.lambda$and$0">javafx.base@26-ea/javafx.util.Subscription.lambda$and$0</a>(Subscription.java:103)
...
```
While it's unusual (and in most cases a very bad idea) to chain that
many Subscriptions, I don't think this should give a StackOverflow
Error. I believe it is better to avoid recursion in the
implementation. If people agree, I'll file an issue for this.
- Johan
</pre>
</blockquote>
</blockquote>
</body>
</html>