Skip to content
This repository has been archived by the owner on Jun 21, 2018. It is now read-only.

g-andrade/nlocks

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

38 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

nlocks

Copyright (c) 2016 Guilherme Andrade

Version: 1.1.2

Authors: Guilherme Andrade (nlocks(at)gandrade(dot)net).

nlocks: Native spinlocks for Erlang


An experiment on Erlang native spinlocks:

  • Able to guarantee exclusive access to shared resources
  • Not dependent on a single process (no flooded mailbox as a bottleneck)
  • Able to deal with brutally killed processes that still held unreleased locks (more below)
Lock = nlocks:new(),
Transaction = fun() ->
    io:format("hello from ~p at ~p~n", [self(), os:timestamp()]),
    timer:sleep(100)
end,

[spawn(fun () -> nlocks:transaction(Lock, Transaction) end) || _ <- lists:seq(1, 4)].
% hello from <0.66.0> at {1465,509871,454813}
% hello from <0.69.0> at {1465,509871,555028}
% hello from <0.67.0> at {1465,509871,656021}
% hello from <0.68.0> at {1465,509871,757031}

Hackish solution. Other than setting up some sort of monitor in the Erlang land, I found no practical way to deal with these other than making use of ownership objects, references to which should never leave the process under which they were created; the release therefore becomes dependent on the garbage collector calling their destructor, but this also makes it more convenient for regularly terminated processes that forget to clean up.

struct Ownership {
    ERL_NIF_TERM pid;
    Lock** lockResource;
};
  • They're created for lock acquisition in order to deal with the above problem
  • They should never leave their owning process
  • On successful acquisition, they link themselves to the lock and force it to live as long as they live
  • They guarantee only the lock owner can release the lock
  • No explicit destruction needed
struct Lock {
    std::atomic<Ownership*> ownership;
};
  • They keep an atomic pointer either set to null (free) or to the ownership object's address (locked)
  • Can be shared between local processes through messages, ETS tables, etc.
  • No explicit destruction needed
  • It uses C++ 11 compare_exchange_weak
  • For each attempt, a new NIF call is scheduled; this brings some overhead to the table but it's crucial to play nice with the VM
  • If the timeout is other than 'infinity', for each attempt it compares the deadline against the current steady_clock value; this feels excessive and might hurt performance.
% nlocks:info()
[{allocated_locks,2},
 {allocated_ownerships,6},
 {acquired_locks,1},
 {contention,5}, % amount of processes attempting lock acquisition
 {has_lockfree_counters,true},
 {has_lockfree_ownership,true}]

Modules

nlocks