--- Coro/Coro.pm 2001/11/06 20:37:20 1.42 +++ Coro/Coro.pm 2006/12/01 02:17:37 1.91 @@ -32,19 +32,24 @@ package Coro; -no warnings qw(uninitialized); +use strict; +no warnings "uninitialized"; use Coro::State; -use base Exporter; +use base qw(Coro::State Exporter); -$VERSION = 0.52; +our $idle; # idle handler +our $main; # main coroutine +our $current; # current coroutine -@EXPORT = qw(async cede schedule terminate current); -%EXPORT_TAGS = ( +our $VERSION = '3.0'; + +our @EXPORT = qw(async cede schedule terminate current); +our %EXPORT_TAGS = ( prio => [qw(PRIO_MAX PRIO_HIGH PRIO_NORMAL PRIO_LOW PRIO_IDLE PRIO_MIN)], ); -@EXPORT_OK = @{$EXPORT_TAGS{prio}}; +our @EXPORT_OK = @{$EXPORT_TAGS{prio}}; { my @async; @@ -52,7 +57,10 @@ # this way of handling attributes simply is NOT scalable ;() sub import { + no strict 'refs'; + Coro->export_to_level(1, @_); + my $old = *{(caller)[0]."::MODIFY_CODE_ATTRIBUTES"}{CODE}; *{(caller)[0]."::MODIFY_CODE_ATTRIBUTES"} = sub { my ($package, $ref) = (shift, shift); @@ -77,17 +85,24 @@ } +=over 4 + =item $main This coroutine represents the main program. =cut -our $main = new Coro; +$main = new Coro; =item $current (or as function: current) -The current coroutine (the last coroutine switched to). The initial value is C<$main> (of course). +The current coroutine (the last coroutine switched to). The initial value +is C<$main> (of course). + +This variable is B I. It is provided for performance +reasons. If performance is not essentiel you are encouraged to use the +C function instead. =cut @@ -96,29 +111,35 @@ $main->{specific} = $current->{specific}; } -our $current = $main; +$current = $main; sub current() { $current } =item $idle -The coroutine to switch to when no other coroutine is running. The default -implementation prints "FATAL: deadlock detected" and exits. +A callback that is called whenever the scheduler finds no ready coroutines +to run. The default implementation prints "FATAL: deadlock detected" and +exits, because the program has no other way to continue. + +This hook is overwritten by modules such as C and +C to wait on an external event that hopefully wake up a +coroutine so the scheduler can run it. + +Please note that if your callback recursively invokes perl (e.g. for event +handlers), then it must be prepared to be called recursively. =cut -# should be done using priorities :( -our $idle = new Coro sub { +$idle = sub { print STDERR "FATAL: deadlock detected\n"; - exit(51); + exit (51); }; # this coroutine is necessary because a coroutine # cannot destroy itself. my @destroy; -my $manager; -$manager = new Coro sub { - while() { +my $manager; $manager = new Coro sub { + while () { # by overwriting the state object with the manager we destroy it # while still being able to schedule this coroutine (in case it has # been readied multiple times. this is harmless since the manager @@ -128,7 +149,12 @@ my $coro = pop @destroy; $coro->{status} ||= []; $_->ready for @{delete $coro->{join} || []}; - $coro->{_coro_state} = $manager->{_coro_state}; + + # the next line destroys the coro state, but keeps the + # process itself intact (we basically make it a zombie + # process that always runs the manager thread, so it's possible + # to transfer() to this process). + $coro->_clone_state_from ($manager); } &schedule; } @@ -136,6 +162,8 @@ # static methods. not really. +=back + =head2 STATIC METHODS Static methods are actually functions that operate on the current process only. @@ -148,28 +176,49 @@ (usually unused). When the sub returns the new process is automatically terminated. +Calling C in a coroutine will not work correctly, so do not do that. + +When the coroutine dies, the program will exit, just as in the main +program. + # create a new coroutine that just prints its arguments async { print "@_\n"; } 1,2,3,4; -The coderef you submit MUST NOT be a closure that refers to variables -in an outer scope. This does NOT work. Pass arguments into it instead. - =cut sub async(&@) { my $pid = new Coro @_; - $manager->ready; # this ensures that the stack is cloned from the manager $pid->ready; - $pid; + $pid } =item schedule Calls the scheduler. Please note that the current process will not be put into the ready queue, so calling this function usually means you will -never be called again. +never be called again unless something else (e.g. an event handler) calls +ready. + +The canonical way to wait on external events is this: + + { + # remember current process + my $current = $Coro::current; + + # register a hypothetical event handler + on_event_invoke sub { + # wake up sleeping coroutine + $current->ready; + undef $current; + }; + + # call schedule until event occured. + # in case we are woken up for other reasons + # (current still defined), loop. + Coro::schedule while $current; + } =cut @@ -183,17 +232,12 @@ =item terminate [arg...] -Terminates the current process. - -Future versions of this function will allow result arguments. +Terminates the current process with the given status values (see L). =cut sub terminate { - $current->{status} = [@_]; - $current->cancel; - &schedule; - die; # NORETURN + $current->cancel (@_); } =back @@ -213,42 +257,50 @@ called. To make the process run you must first put it into the ready queue by calling the ready method. +Calling C in a coroutine will not work correctly, so do not do that. + =cut -sub _newcoro { +sub _new_coro { terminate &{+shift}; } sub new { my $class = shift; - bless { - _coro_state => (new Coro::State $_[0] && \&_newcoro, @_), - }, $class; + + $class->SUPER::new (\&_new_coro, @_) } -=item $process->ready +=item $success = $process->ready -Put the given process into the ready queue. +Put the given process into the ready queue (according to it's priority) +and return true. If the process is already in the ready queue, do nothing +and return false. -=cut +=item $is_ready = $process->is_ready + +Return wether the process is currently the ready queue or not, -=item $process->cancel +=item $process->cancel (arg...) -Like C, but terminates the specified process instead. +Terminates the given process and makes it return the given arguments as +status (default: the empty list). =cut sub cancel { - push @destroy, $_[0]; + my $self = shift; + $self->{status} = [@_]; + push @destroy, $self; $manager->ready; - &schedule if $current == $_[0]; + &schedule if $current == $self; } =item $process->join Wait until the coroutine terminates and return any values given to the -C function. C can be called multiple times from multiple -processes. +C or C functions. C can be called multiple times +from multiple processes. =cut @@ -261,11 +313,11 @@ wantarray ? @{$self->{status}} : $self->{status}[0]; } -=item $oldprio = $process->prio($newprio) +=item $oldprio = $process->prio ($newprio) Sets (or gets, if the argument is missing) the priority of the process. Higher priority processes get run before lower priority -processes. Priorities are smalled signed integer (currently -4 .. +3), +processes. Priorities are small signed integers (currently -4 .. +3), that you can refer to using PRIO_xxx constants (use the import tag :prio to get then): @@ -283,26 +335,12 @@ running) will only take effect after the next schedule (of that process). This is a bug that will be fixed in some future version. -=cut - -sub prio { - my $old = $_[0]{prio}; - $_[0]{prio} = $_[1] if @_ > 1; - $old; -} - -=item $newprio = $process->nice($change) +=item $newprio = $process->nice ($change) Similar to C, but subtract the given value from the priority (i.e. higher values mean lower priority, just as in unix). -=cut - -sub nice { - $_[0]{prio} -= $_[1]; -} - -=item $olddesc = $process->desc($newdesc) +=item $olddesc = $process->desc ($newdesc) Sets (or gets in case the argument is missing) the description for this process. This is just a free-form string you can associate with a process. @@ -323,22 +361,28 @@ =head1 BUGS/LIMITATIONS - - you must make very sure that no coro is still active on global destruction. - very bad things might happen otherwise (usually segfaults). - - this module is not thread-safe. You should only ever use this module from - the same thread (this requirement might be loosened in the future to - allow per-thread schedulers, but Coro::State does not yet allow this). + - you must make very sure that no coro is still active on global + destruction. very bad things might happen otherwise (usually segfaults). + + - this module is not thread-safe. You should only ever use this module + from the same thread (this requirement might be losened in the future + to allow per-thread schedulers, but Coro::State does not yet allow + this). =head1 SEE ALSO -L, L, L, L, -L, L, L, L, -L, L. +Support/Utility: L, L, L, L. + +Locking/IPC: L, L, L, L, L. + +Event/IO: L, L, L, L, L. + +Embedding: L =head1 AUTHOR - Marc Lehmann - http://www.goof.com/pcg/marc/ + Marc Lehmann + http://home.schmorp.de/ =cut