8

Consider the following:

[Fact]
public void foo()
{
    var result = new Subject<bool>();
    var startCount = 0;
    var completionCount = 0;
    var obs = Observable
        .Defer(() =>
            {
                ++startCount;
                return result.FirstAsync();
            })
        .Do(_ => ++completionCount)
        .Publish()
        .RefCount();

    // pretend there are lots of subscribers at once
    var s1 = obs.Subscribe();
    var s2 = obs.Subscribe();
    var s3 = obs.Subscribe();

    // even so, we only expect to be started once
    Assert.Equal(1, startCount);
    Assert.Equal(0, completionCount);

    // and we won't complete until the result ticks through
    result.OnNext(true);
    Assert.Equal(1, startCount);
    Assert.Equal(1, completionCount);

    s1.Dispose();
    s2.Dispose();
    s3.Dispose();

    // now try exactly the same thing again
    s1 = obs.Subscribe();
    s2 = obs.Subscribe();
    s3 = obs.Subscribe();

    // startCount is 4 here instead of the expected 2!
    Assert.Equal(2, startCount);
    Assert.Equal(1, completionCount);

    result.OnNext(true);
    Assert.Equal(2, startCount);
    Assert.Equal(2, completionCount);

    s1.Dispose();
    s2.Dispose();
    s3.Dispose();
}

My understanding of Publish + RefCount is that a connection to the source is maintained as long as there is at least one subscriber. Once the last subscriber disconnects, any future subscriber will re-initiate the connection to the source.

As you can see in my test, everything works perfectly the first time through. But the second time, the deferred observable inside the pipeline is executed once for every new subscriber.

I can see via the debugger that for the first group of subscribers, obs._count (which counts subscribers) increases for each call to Subscribe. But for the second group of subscribers, it remains zero.

Why is this happening and what can I do to rectify my pipeline?

Kent Boogaart
  • 175,602
  • 35
  • 392
  • 393

2 Answers2

3

The answer from @user631090 is close, but incorrect, so I thought I'd answer myself.

It's because Publish will immediately complete new subscribers if the stream it published has itself completed. You can kind of see that in the diagram here:

enter image description here

But it would have been nice if the diagram included a subscriber after the underlying stream completes.

To add to the confusion, Defer is still called for new subscribers. But its return value is simply ignored by Publish because of the initial stream completing.

I'm as yet unable to come up with a way to implement my intended use case. I thought perhaps using Multicast rather than Publish, creating a new subject as necessary. But I haven't been able to achieve that yet. And it seems rather painful for what I would think is a common use case.

Kent Boogaart
  • 175,602
  • 35
  • 392
  • 393
  • 2
    Kent, would you mind explaining your intended use case (in another post)? Maybe the community can help you more directly there. (potentially reducing the amount of moving parts : Subject + Defer + First + Publish + Refcount and giving a problem (not a bug) may allow us to help more. – Lee Campbell Mar 01 '16 at 03:17
  • Sure Lee. I've just posted this question as a follow-up: http://stackoverflow.com/questions/35762063/why-is-refcount-not-working-after-all-initial-subscribers-disconnect-redux – Kent Boogaart Mar 03 '16 at 02:29
1

It's because the underlying observable result has already completed. So each new subscriber is just getting the OnCompleted callback.

If ObservableDefer was creating a new sequence each time or one that didn't complete you would see the desired behavior.

e.g.

return result.FirstAsync().Concat(Observable.Never<bool>());

You will need to remove the Assert.Equal(1, completionCount);

user630190
  • 1,142
  • 2
  • 11
  • 26
  • This sounds plausible, but I'm having difficulty producing a sequence that would work as expected. I had thought that `return result.Take(1);` rather than `return result.FirstAsync();` would have worked, but I'm getting the same result. Quite curious., – Enigmativity Feb 29 '16 at 09:01
  • `result` has _not_ completed. Each individual call to `result.FirstAsync` will complete when a new value is ticked. – Kent Boogaart Feb 29 '16 at 09:28
  • I should have said result.FirstAsync has completed. I agree the behavior is strange. It looks like this is somehow captured so future subscribers get back the completed observable. You can see this by hooking up your second set of subscribers OnCompleted events, they fire immediately rather than wait for you to send another .OnNext. Hence each new subscriber increments the count. – user630190 Feb 29 '16 at 11:04
  • `result.FirstAsync` has not completed either. Even if I create an entirely new `Subject` in the `Defer` handler, I get the same behavior. So it's more like `Defer` is correctly being invoked to obtain the next observable, but then it's being ignored. Possibly a bug with `Publish` / `RefCount`? – Kent Boogaart Feb 29 '16 at 12:35