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:
defaultSubscription and(Subscription other) { Objects.requireNonNull(other, "other cannot be null"); returnnewNodeSubscription(this, other); } // TODOmove to package to make it package private staticclassNodeSubscription implementsSubscription { privatefinalSubscription s1; privatefinalSubscription s2; publicNodeSubscription(Subscription s1, Subscription s2) { booleanshouldSwap = s1 instanceofNodeSubscription; // attempt to avoid growing stack as much as possible this.s1= shouldSwap ? s2 : s1; this.s2= shouldSwap ? s1 : s2; } @Override publicvoidunsubscribe() { List<Subscription> stack = newArrayList<>(); stack.add(this); while(!stack.isEmpty()) { Subscription s = stack.removeLast(); if(s instanceofNodeSubscription n) { stack.add(n.s1); stack.add(n.s2); } else{ s.unsubscribe(); } } } } Note: as prescribed by the specification, `unsubscribe` is idempotent. --John On 13/08/2025 22:11, John Hendrikx wrote: > 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: >> 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 >> javafx.base@26-ea/javafx.util.Subscription.lambda$and$0(Subscription.java:103) >> at >> javafx.base@26-ea/javafx.util.Subscription.lambda$and$0(Subscription.java:103) >> at >> javafx.base@26-ea/javafx.util.Subscription.lambda$and$0(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