Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Thread safe slot maps without containers #1785

Open
wants to merge 13 commits into
base: master
Choose a base branch
from

Conversation

aardvark179
Copy link
Contributor

Thread safe slot maps without containers

This is a stacked PR on top of

To enable thread safe slot maps without containers we want two properties to hold true:

  1. For empty and single entry maps which do not require a lock we must to a compare and exchange rather than a simple assignment when setting the map, if the exchange fails then we perform the operation with the new map. The number of times this can happen is strictly limited by the chain of slot map promotions.
  2. For larger maps that do require a lock we need to ensure the operation can be performed on the currently installed map at the time the lock is acquired.

Compare and exchange operation

We utilise the java.lang.invoke.VarHandle methods to perform this operation. These are not available on older versions of Android, or Java 8, but if thread safe maps are required in those environments then SlotMapOwner could be refactored to use a java.util.concurrent.atomic.AtomicReference at the expense of one object (and one level of indirection).

Promotion and lock sharing

Empty maps to single entry maps

Promoting from the empty map to a single entry slot map is simple, the empty map and single entry maps are immutable so we can construct the single entry map, perform the compare and exchange, and then we are either done, or we ask the currently installed map to perform the operation.

Single entry maps to larger maps

Again, we can construct the larger ThreadSafeEmbeddedSlotMap and perform a compare and exchange operation to install it. The exception to this is the compute operation, where we install the map (as we know a mutation will occur) and then perform the compute operation on this new map. This is done to avoid any side effects that might result from performing the compute operation twice.

Promotion between large maps

The promotion of new maps looks something like the following. Blue links represent references that exist up until the new map is installed, and red linked represent objects and references that only exist in the new map. Since old map cannot be mutated, nor the new map installed, without a lock this operation does not need to be entirely atomic. Note that both the old and new maps have the same lock object.

graph LR
Object["ScriptableObject"]
Lock["StampedLock"]
Map["OldSlotMap"]
Array["Slot[]"]
Slot1["Slot"]
Slot2["Slot"]
Slot3["Slot"]
Object --> Map
Map --> Array
Map --> Lock
Array --> Slot1
Array --> Slot2
Array --> Slot3
Map1["NewSlotMap"]
Array1["Slot[]"]
Slot4["Slot"]
Slot5["Slot"]
Slot6["Slot"]
Object --> Map1
Map1 --> Array1
Map1 --> Lock
Map --> Map1
Array1 --> Slot4
Array1 --> Slot5
Array1 --> Slot6
style Map1 stroke:red
style Array1 stroke:red
style Slot4 stroke:red
style Slot5 stroke:red
style Slot6 stroke:red
linkStyle 0 stroke:blue
linkStyle 6 stroke:red
linkStyle 8 stroke:red
linkStyle 9 stroke:red
linkStyle 10 stroke:red
linkStyle 11 stroke:red
linkStyle 12 stroke:red
Loading

This approach of sharing a single lock enables sequences like the following to work correctly:

sequenceDiagram
    Thread1 ->>+ OldMap: Acquires lock
    Thread2 ->>+ OldMap: Wait for lock
    Thread1 ->>+ OldMap: Promote map
    OldMap ->>+ NewMap: Replace map
    Thread1 ->>+ OldMap: Release lock
    Thread2 ->>+ OldMap: Acquire lock
    Thread2 ->>+ OldMap: Perform action
    Thread1 ->>+ NewMap: Wait for lock
    OldMap ->>+ NewMap: Delegate action
    Thread2 ->>+ OldMap: Release lock
    Thread1 ->>+ NewMap: Acquire Loc
Loading

@aardvark179 aardvark179 marked this pull request as ready for review December 30, 2024 16:31
@rbri
Copy link
Collaborator

rbri commented Dec 30, 2024

Another really interesting observation i guess the same is true for htmlunit. Will try it next year 😉

@aardvark179 aardvark179 force-pushed the aardvark179-remove-thread-safe-map-continaer branch from 2a70a9a to 5b1569b Compare January 9, 2025 20:14
@aardvark179
Copy link
Contributor Author

Rebased after merge of #1782.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants