Codebase list libfuture-asyncawait-perl / upstream/0.34
upstream/0.34

Tree @upstream/0.34 (Download .tar.gz)

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
NAME

    Future::AsyncAwait - deferred subroutine syntax for futures

SYNOPSIS

       use Future::AsyncAwait;
    
       async sub do_a_thing
       {
          my $first = await do_first_thing();
    
          my $second = await do_second_thing();
    
          return combine_things( $first, $second );
       }
    
       do_a_thing()->get;

DESCRIPTION

    This module provides syntax for deferring and resuming subroutines
    while waiting for Futures to complete. This syntax aims to make code
    that performs asynchronous operations using futures look neater and
    more expressive than simply using then chaining and other techniques on
    the futures themselves. It is also a similar syntax used by a number of
    other languages; notably C# 5, EcmaScript 6, Python 3, Dart. Rust is
    considering adding it.

    The new syntax takes the form of two new keywords, async and await.

 async

    The async keyword should appear just before the sub keyword that
    declares a new function. When present, this marks that the function
    performs its work in a potentially asynchronous fashion. This has two
    effects: it permits the body of the function to use the await
    expression, and it wraps the return value of the function in a Future
    instance.

       async sub myfunc
       {
          return 123;
       }
    
       my $f = myfunc();
       my $result = $f->get;

    This async-declared function always returns a Future instance when
    invoked. The returned future instance will eventually complete when the
    function returns, either by the return keyword or by falling off the
    end; the result of the future will be the return value from the
    function's code. Alternatively, if the function body throws an
    exception, this will cause the returned future to fail.

    If the final expression in the body of the function returns a Future,
    don't forget to await it rather than simply returning it as it is, or
    else this return value will become double-wrapped - almost certainly
    not what you wanted.

       async sub otherfunc { ... }
    
       async sub myfunc
       {
          ...
          return await otherfunc();
       }

 await

    The await keyword forms an expression which takes a Future instance as
    an operand and yields the eventual result of it. Superficially it can
    be thought of similar to invoking the get method on the future.

       my $result = await $f;
    
       my $result = $f->get;

    However, the key difference (and indeed the entire reason for being a
    new syntax keyword) is the behaviour when the future is still pending
    and is not yet complete. Whereas the simple get method would block
    until the future is complete, the await keyword causes its entire
    containing function to become suspended, making it return a new
    (pending) future instance. It waits in this state until the future it
    was waiting on completes, at which point it wakes up and resumes
    execution from the point of the await expression. When the now-resumed
    function eventually finishes (either by returning a value or throwing
    an exception), this value is set as the result of the future it had
    returned earlier.

    Because the await keyword may cause its containing function to suspend
    early, returning a pending future instance, it is only allowed inside
    async-marked subs.

    The converse is not true; just because a function is marked as async
    does not require it to make use of the await expression. It is still
    useful to turn the result of that function into a future, entirely
    without awaiting on any itself.

    Any function that doesn't actually await anything, and just returns
    immediate futures can be neatened by this module too.

    Instead of writing

       sub imm
       {
          ...
          return Future->done( @result );
       }

    you can now simply write

       async sub imm
       {
          ...
          return @result;
       }

    with the added side-benefit that any exceptions thrown by the elided
    code will be turned into an immediate-failed Future rather than making
    the call itself propagate the exception, which is usually what you
    wanted when dealing with futures.

STABILITY WARNING

    This module is still relatively new and under active development. While
    it now seems relatively stable enough for most use-cases, there may
    still be a number of memory leaks left in it, especially if
    still-pending futures are abandoned.

    While it seems stable enough for small-scale development and
    experimental testing, take care when using this module in production,
    as some growth in memory over time may be observed. Careful use of
    monitoring and periodic restarts of long-running processes may be a
    wise precaution.

    That said, using this module in places like unit-tests and short-term
    scripts does appear to be quite stable, so do try experimenting with it
    in this sort of situation, and let me know what does and doesn't work.

SUPPORTED USES

    Most cases involving awaiting on still-pending futures should work
    fine:

       async sub foo
       {
          my ( $f ) = @_;
    
          BEFORE();
          await $f;
          AFTER();
       }
    
       async sub bar
       {
          my ( $f ) = @_;
    
          return 1 + await( $f ) + 3;
       }
    
       async sub splot
       {
          while( COND ) {
             await func();
          }
       }
    
       async sub wibble
       {
          if( COND ) {
             await func();
          }
       }
    
       async sub wobble
       {
          foreach my $var ( THINGs ) {
             await func();
          }
       }
    
       async sub quux
       {
          my $x = do {
             await func();
          };
       }
    
       async sub splat
       {
          eval {
             await func();
          };
       }

    Plain lexical variables are preserved across an await deferral:

       async sub quux
       {
          my $message = "Hello, world\n";
          await func();
          print $message;
       }

    On perl versions 5.26 and later async sub syntax supports the
    signatures feature if it is enabled:

       use 5.026;
       use feature 'signatures';
    
       async sub quart($x, $y)
       {
          ...
       }

 Cancellation

    Cancelled futures cause a suspended async sub to simply stop running.

       async sub fizz
       {
          await func();
          say "This is never reached";
       }
    
       my $f = fizz();
       $f->cancel;

    Cancellation requests can propagate backwards into the future the async
    sub is currently waiting on.

       async sub floof
       {
          ...
          await $f1;
       }
    
       my $f2 = floof();
    
       $f2->cancel;  # $f1 will be cancelled too

    This behaviour is still more experimental than the rest of the logic.
    The following should be noted:

      * There is currently no way to perform the equivalent of "on_cancel"
      in Future to add a cancellation callback to a future chain.

      * Cancellation propagation is only implemented on Perl version 5.24
      and above. An async sub in an earlier perl version will still stop
      executing if cancelled, but will not propagate the request backwards
      into the future that the async sub is currently waiting on. See
      "TODO".

WITH OTHER MODULES

 Syntax::Keyword::Try

    As of Future::AsyncAwait version 0.10 and Syntax::Keyword::Try version
    0.07, cross-module integration tests assert that basic try/catch blocks
    inside an async sub work correctly, including those that attempt to
    return from inside try.

       use Future::AsyncAwait;
       use Syntax::Keyword::Try;
    
       async sub attempt
       {
          try {
             await func();
             return "success";
          }
          catch {
             return "failed";
          }
       }

 Syntax::Keyword::Dynamically

    As of Future::AsyncAwait version 0.32, cross-module integration tests
    assert that the dynamically correctly works across an await boundary.

       use Future::AsyncAwait;
       use Syntax::Keyword::Dynamically;
    
       our $var;
    
       async sub trial
       {
          dynamically $var = "value";
    
          await func();
    
          say "Var is still $var";
       }

SEE ALSO

      * "Awaiting The Future" - TPC in Amsterdam 2017

      https://www.youtube.com/watch?v=Xf7rStpNaT0 (slides)
      <https://docs.google.com/presentation/d/13x5l8Rohv_RjWJ0OTvbsWMXKoNEWREZ4GfKHVykqUvc/edit#slide=id.p>

TODO

      * Suspend and resume with some consideration for the savestack; i.e.
      the area used to implement local and similar. While in general local
      support has awkward questions about semantics, there are certain
      situations and cases where internally-implied localisation of
      variables would still be useful and can be supported without the
      semantic ambiguities of generic local.

         our $DEBUG = 0;
      
         async sub quark
         {
            local $DEBUG = 1;
            await func();
         }

      Since foreach loops on non-lexical iterator variables (usually the $_
      global variable) effectively imply a local-like behaviour, these are
      also disallowed.

         async sub splurt
         {
            foreach ( LIST ) {
               await ...
            }
         }

      Some notes on what makes the problem hard can be found at

      https://rt.cpan.org/Ticket/Display.html?id=122793

      * Currently this module requires perl version 5.16 or later.
      Additionally, threaded builds of perl earlier than 5.22 are not
      supported.

      https://rt.cpan.org/Ticket/Display.html?id=122252

      https://rt.cpan.org/Ticket/Display.html?id=124351

      * Implement cancel back-propagation for Perl versions earlier than
      5.24. Currently this does not work due to some as-yet-unknown effects
      that installing the back-propagation has, causing future instances to
      be reclaimed too early.

      https://rt.cpan.org/Ticket/Display.html?id=129202

KNOWN BUGS

    This is not a complete list of all known issues, but rather a summary
    of the most notable ones that currently prevent the module from working
    correctly in a variety of situations. For a complete list of known
    bugs, see the RT queue at
    https://rt.cpan.org/Dist/Display.html?Name=Future-AsyncAwait.

      * await inside map or grep blocks does not work. This is due to the
      difficulty of detecting the map or grep context from internal perl
      state at suspend time, sufficient to be able to restore it again when
      resuming.

      https://rt.cpan.org/Ticket/Display.html?id=129748

      As a workaround, consider converting a map expression to the
      equivalent form using push onto an accumulator array with a foreach
      loop:

         my @results = map { await func($_) } ITEMS;

      becomes

         my @results;
         foreach my $item ( ITEMS ) {
            push @results, await func($item);
         }

      with a similar transformation for grep expressions.

      Alternatively, consider using the fmap* family of functions from
      Future::Utils to provide a concurrent version of the same code, which
      can keep multiple items running concurrently:

         use Future::Utils qw( fmap );
      
         my @results = await fmap { func( shift ) }
            foreach    => [ ITEMS ],
            concurrent => 5;

ACKNOWLEDGEMENTS

    With thanks to Zefram, ilmari and others from irc.perl.org/#p5p for
    assisting with trickier bits of XS logic.

    Thanks to genio for project management and actually reminding me to
    write some code.

    Thanks to The Perl Foundation for sponsoring me to continue working on
    the implementation.

AUTHOR

    Paul Evans <leonerd@leonerd.org.uk>