Acme-Parataxis

 view release on metacpan or  search on metacpan

lib/Acme/Parataxis/Sync/Mutex.pm  view on Meta::CPAN

    use Acme::Parataxis;
    use Carp qw[croak];
    field $owner : reader;    # fiber id holding the lock; undef when free
    field @waiters;           # fiber ids waiting to lock, FIFO

    # Acquire the lock, parking until it is handed over. The lock is not reentrant: a fiber that already holds it croaks.
    method lock {
        my $fid = $self->_fid('Mutex locks must occur inside a scheduled fiber');
        if ( !defined $owner ) { $owner = $fid; return 1 }
        croak 'Mutex is not reentrant: this fiber already holds the lock' if $owner == $fid;
        push @waiters, $fid;
        $self->_park(
            'Mutex lock',
            sub {
                # An unlock() may have handed the lock to us just as an interrupt (deadline/cancel) fired. That hand-off
                # is never collected -- the interrupt throws before lock() returns -- so pass it on so ownership is not
                # left stranded on a fid that is about to die (and later be reused).
                if ( defined $owner && $owner == $fid ) {
                    $owner = undef;
                    $self->_serve;
                }
                $self->remove_waiter($fid);
            }
        );
        return 1;    # unlock() handed the lock to us before waking us
    }

    # Non-blocking acquisition; returns false instead of parking when the lock is held.
    method try_lock {
        my $fid = $self->_fid('Mutex locks must occur inside a scheduled fiber');
        return 0                                                          if defined $owner && $owner != $fid;
        croak 'Mutex is not reentrant: this fiber already holds the lock' if defined $owner;
        $owner = $fid;
        return 1;
    }

    # Release the lock to the next waiter (or free it). Only the owning fiber may release.
    method unlock {
        my $fid = $self->_fid('Mutex unlocks must occur inside a scheduled fiber');
        croak 'Mutex is not locked'                              unless defined $owner;
        croak 'Mutex release from a fiber that is not the owner' unless $owner == $fid;
        $self->_serve;
        return 1;
    }

    # Hand the lock to the next living waiter, or free it. owner is set before the waiter is woken so nobody else can
    # steal the lock between the hand-off and the waiter running to collect it.
    method _serve {
        my $next;
        while (@waiters) {
            my $waiter = shift @waiters;
            next unless defined Acme::Parataxis->by_id($waiter);
            $next = $waiter;
            last;
        }
        $owner = $next;    # hand-off is atomic: nobody else can steal it before the waiter runs
        Acme::Parataxis::_scheduler_enqueue_by_id($next) if defined $next;
        return 1;
    }

    # Return a Scope::Guard-style object that unlocks on destruction.
    method guard {
        $self->lock;
        Acme::Parataxis::Sync::Mutex::Guard->new( mutex => $self );
    }
    method waiters { scalar @waiters }

    method remove_waiter ($fid) {    # unregister a parked waiter (used by the lock park's interruption path)
        my $before = @waiters;
        @waiters = grep { $_ != $fid } @waiters;
        return $before - @waiters;
    }
};

# Util. A classic package, not feature::class: a perlclass 'method DESTROY' is one trigger of the perl_clone access
# violation behind threads->create on some 5.42.x builds (Acme::Parataxis::Blocking's spawn_blocking), so guard classes
# are kept as classic blessed packages. Note: the Sync::* namespaces themselves still hit a separate, name-dependent
# clone crash on affected perls regardless of this - see Acme::Parataxis::Blocking's pod warning.
package Acme::Parataxis::Sync::Mutex::Guard v0.1.3 {

    sub new ( $class, %args ) {
        return bless { mutex => $args{mutex} }, $class;
    }

    sub DESTROY {
        my $self = shift;
        return if ${^GLOBAL_PHASE} eq 'DESTRUCT';
        $self->{mutex}->unlock;
    }
}
#
1;



( run in 1.734 second using v1.01-cache-2.11-cpan-b554fb43c41 )