Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Wait on a Supply after begin tap is hanging #5658

Closed
p6rt opened this issue Sep 11, 2016 · 6 comments
Closed

Wait on a Supply after begin tap is hanging #5658

p6rt opened this issue Sep 11, 2016 · 6 comments

Comments

@p6rt
Copy link

p6rt commented Sep 11, 2016

Migrated from rt.perl.org#129247 (status was 'resolved')

Searchable as RT129247$

@p6rt
Copy link
Author

p6rt commented Sep 11, 2016

From pierre.vigier@gmail.com

Hi,

i stumbled a across a strange behaviour when playing with Supplier, i tried to gulf it down, starting from the documentation of supply. The following code, taken from the documentation and slightly modified is working as expected​:

my $s = Supplier.new;
my $l = $s.Supply();
start {
  sleep 1;
  $s.emit(1);
  $s.emit(2);
  $s.done;
}
$l.wait;
say "done";

If i tap the $l variable to display the value that are supplied by the supplier, it then hangs indefinitely​:

my $s = Supplier.new;
my $l = $s.Supply();
$l.tap( -> $v { say "got : $v" } );
start {
  sleep 1;
  $s.emit(1);
  $s.emit(2);
  $s.done;
}
$l.wait;
say "done";

Pierre

@p6rt
Copy link
Author

p6rt commented Sep 11, 2016

From @zoffixznet

On Sun Sep 11 07​:01​:11 2016, pierre.vigier@​gmail.com wrote​:

Hi,

i stumbled a across a strange behaviour when playing with Supplier, i
tried to gulf it down, starting from the documentation of supply. The
following code, taken from the documentation and slightly modified is
working as expected​:

my $s = Supplier.new;
my $l = $s.Supply();
start {
sleep 1;
$s.emit(1);
$s.emit(2);
$s.done;
}
$l.wait;
say "done";

If i tap the $l variable to display the value that are supplied by the
supplier, it then hangs indefinitely​:

my $s = Supplier.new;
my $l = $s.Supply();
$l.tap( -> $v { say "got : $v" } );
start {
sleep 1;
$s.emit(1);
$s.emit(2);
$s.done;
}
$l.wait;
say "done";

Pierre

I tried to debug this, but ultimately failed, as I'm getting spectest failures.

There are actually three issues​:
1) Supply.sanitize uses a per-instance $!finished attribute and when the first tap gets closed, no more taps get closed. This is what causes the OP bug.
2) Supply.wait calls Supply.Promise that opens a new tap that keeps/breaks the Promise on done/quit. However, if this is done on-already finished Supply, that Promise will never complete, causing in a hang in .wait. This is what causes the bug if we golf OP's code to get rid of the start {}.
3) To me, it looks like there's a race condition with regards to $!finished, since one tap can be setting it to `1`, while another is mid-checking it. Not sure if it matters.

Below is my attempt at fixing it that fixes issues #​1 and #​2 (where .wait on a finished supply returns a Nil), however, this has failures in t/spec/S17-supply/basic.t because I'm not returning a Tap, I think.

I've also no idea why the created supplies go through the .serialize -> .sanitize chain where both methods look nearly identical. I'm leaving this for someone more knowledgeable to finish.

----------------------8<-----------------------------------------

Inline Patch
diff --git a/src/core/Supply.pm b/src/core/Supply.pm
index d91c3b8..f0fa251 100644
--- a/src/core/Supply.pm
+++ b/src/core/Supply.pm
@@ -197,6 +197,7 @@ my class Supply {
 
             method tap(&emit, &done, &quit) {
                 my int $cleaned-up = 0;
+                return if $!finished;
                 my $source-tap = $!source.tap(
                     -> \value{
                         emit(value) unless $!finished;
@@ -204,16 +205,16 @@ my class Supply {
                     done => -> {
                         unless $!finished {
                             $!finished = 1;
-                            done();
                             self!cleanup($cleaned-up, $source-tap);
                         }
+                        done();
                     },
                     quit => -> $ex {
                         unless $!finished {
                             $!finished = 1;
-                            quit($ex);
                             self!cleanup($cleaned-up, $source-tap);
                         }
+                        quit($ex);
                     });
                 Tap.new({ self!cleanup($cleaned-up, $source-tap) })
             }
@@ -610,10 +611,11 @@ my class Supply {
         my $p = Promise.new;
         my $v = $p.vow;
         my $final := Nil;
-        my $t = self.tap:
+        my $t = self.tap(
             -> \val { $final := val },
             done => { $v.keep($final) },
-            quit => -> \ex { $v.break(ex) };
+            quit => -> \ex { $v.break(ex) },
+        ) or $v.keep(Nil);
         $p
     }
----------------------8<-----------------------------------------

@p6rt
Copy link
Author

p6rt commented Sep 11, 2016

The RT System itself - Status changed from 'new' to 'open'

@p6rt
Copy link
Author

p6rt commented Jul 13, 2017

From @ugexe

Resolved in​: rakudo/rakudo@32b72cd

Tests​: Raku/roast@927b026

@p6rt
Copy link
Author

p6rt commented Jul 14, 2017

From @dogbert17

On Thu, 13 Jul 2017 16​:49​:00 -0700, ugexe@​cpan.org wrote​:

Resolved in​: rakudo/rakudo@32b72cd

Tests​: Raku/roast@927b026

Bug fixed and tests added, closing issue.

@p6rt
Copy link
Author

p6rt commented Jul 14, 2017

@dogbert17 - Status changed from 'open' to 'resolved'

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

1 participant