@@ -27,8 +27,10 @@ defmodule Nebulex.Adapters.Local do
2727 (see `Nebulex.Adapters.Local.Generation`).
2828 * Sharding - For intensive workloads, the Cache may also be partitioned
2929 (by using `:shards` backend and specifying the `:partitions` option).
30- * Support for transactions via Erlang global name registration facility.
31- See `Nebulex.Adapter.Transaction`.
30+ * Support for transactions via `Nebulex.Locks`, a lightweight ETS-based
31+ locking mechanism optimized for single-node scenarios. Provides atomic
32+ lock acquisition, deadlock prevention, and automatic stale lock cleanup.
33+ See `Nebulex.Locks` and `Nebulex.Adapter.Transaction`.
3234 * Support for stats.
3335 * Automatic retry logic for handling race conditions during garbage
3436 collection (see [Concurrency and resilience](#module-concurrency-and-resilience)).
@@ -745,11 +747,19 @@ defmodule Nebulex.Adapters.Local do
745747
746748 ## Transaction API
747749
748- This adapter inherits the default implementation provided by
749- `Nebulex.Adapter.Transaction`. Therefore, the `transaction` command accepts
750- the following options:
750+ This adapter implements the `Nebulex.Adapter.Transaction` behaviour using
751+ `Nebulex.Locks`, a lightweight ETS-based locking mechanism optimized for
752+ single-node scenarios. This implementation provides significantly better
753+ performance compared to distributed locking mechanisms (e.g., `:global`)
754+ while maintaining the same transaction API.
751755
752- #{ Nebulex.Adapter.Transaction.Options . options_docs ( ) }
756+ The `transaction` command accepts the following options:
757+
758+ #{ Nebulex.Locks.Options . options_docs ( ) }
759+
760+ The locks manager can be customized via the `:lock_opts` configuration
761+ option when starting the cache. See the configuration options above for
762+ more details.
753763
754764 ## Extended API (extra functions)
755765
@@ -775,9 +785,7 @@ defmodule Nebulex.Adapters.Local do
775785 @ behaviour Nebulex.Adapter
776786 @ behaviour Nebulex.Adapter.KV
777787 @ behaviour Nebulex.Adapter.Queryable
778-
779- # Inherit default transaction implementation
780- use Nebulex.Adapter.Transaction
788+ @ behaviour Nebulex.Adapter.Transaction
781789
782790 # Inherit default info implementation
783791 use Nebulex.Adapters.Common.Info
@@ -790,6 +798,8 @@ defmodule Nebulex.Adapters.Local do
790798
791799 alias Nebulex.Adapters.Common.Info.Stats
792800 alias Nebulex.Adapters.Local . { Backend , Generation , Metadata }
801+ alias Nebulex.Locks
802+ alias Nebulex.Locks.Options , as: LockOptions
793803 alias Nebulex.Time
794804
795805 ## Types & Internal definitions
@@ -1315,6 +1325,61 @@ defmodule Nebulex.Adapters.Local do
13151325 % { total: max_size , used: mem_size }
13161326 end
13171327
1328+ ## Nebulex.Adapter.Transaction
1329+
1330+ @ impl true
1331+ def transaction ( % { cache: cache , pid: pid } = adapter_meta , fun , opts ) do
1332+ opts = LockOptions . validate! ( opts )
1333+
1334+ adapter_meta
1335+ |> do_in_transaction? ( )
1336+ |> do_transaction (
1337+ pid ,
1338+ adapter_meta [ :name ] || cache ,
1339+ adapter_meta . meta_tab ,
1340+ opts ,
1341+ fun
1342+ )
1343+ end
1344+
1345+ @ impl true
1346+ def in_transaction? ( adapter_meta , _opts ) do
1347+ wrap_ok do_in_transaction? ( adapter_meta )
1348+ end
1349+
1350+ defp do_in_transaction? ( % { pid: pid } ) do
1351+ ! ! Process . get ( { pid , self ( ) } )
1352+ end
1353+
1354+ defp do_transaction ( true , _pid , _name , _meta_tab , _opts , fun ) do
1355+ { :ok , fun . ( ) }
1356+ end
1357+
1358+ defp do_transaction ( false , pid , name , meta_tab , opts , fun ) do
1359+ locks_table = Metadata . fetch! ( meta_tab , :locks_table )
1360+ keys = Keyword . fetch! ( opts , :keys )
1361+ ids = lock_ids ( name , keys )
1362+
1363+ case Locks . acquire ( locks_table , ids , opts ) do
1364+ :ok ->
1365+ try do
1366+ _ = Process . put ( { pid , self ( ) } , keys )
1367+
1368+ { :ok , fun . ( ) }
1369+ after
1370+ _ = Process . delete ( { pid , self ( ) } )
1371+
1372+ Locks . release ( locks_table , ids )
1373+ end
1374+
1375+ { :error , :timeout } ->
1376+ wrap_error Nebulex.Error , reason: :transaction_aborted , cache: name
1377+ end
1378+ end
1379+
1380+ defp lock_ids ( name , [ ] ) , do: [ name ]
1381+ defp lock_ids ( name , keys ) , do: Enum . map ( keys , & { name , & 1 } )
1382+
13181383 ## Helpers
13191384
13201385 # Inline common instructions
0 commit comments