Skip to content

Core Abstractions

This page documents every type in the simba-core module. The module defines the interface chain that all backends implement, the value objects that represent ownership state, and the abstract base classes that handle scheduling, notification dispatch, and lifecycle management.

Value Objects

MutexOwner

MutexOwner is an immutable value object that represents the current holder of a distributed mutex. It carries four fields:

FieldTypeDescription
ownerIdStringThe contender ID of the current owner
acquiredAtLongEpoch millis when the lock was acquired
ttlAtLongEpoch millis when the lock's TTL expires (owner must renew before this)
transitionAtLongEpoch millis when the transition period ends (other contenders may attempt acquisition after this)

Key derived properties and methods:

  • isInTtl — returns true when ttlAt > currentTimeMillis(), meaning the owner still has a valid TTL.
  • isInTransition — returns true when transitionAt >= currentTimeMillis(), meaning no other contender should attempt acquisition yet.
  • hasOwner() — returns true when transitionAt >= currentTimeMillis(), indicating that an active leader exists (even if TTL has expired, the transition window still counts as "owned").
  • isOwner(contenderId) — checks whether the given contender ID matches ownerId.

The class is annotated with Guava's @Immutable (line 22).

NONE sentinel: The companion object provides MutexOwner.NONE (line 85), a singleton with ownerId = "", acquiredAt = 0, ttlAt = 0, transitionAt = 0. This represents the absence of any owner and is used as the initial and terminal state.

mermaid
classDiagram
    class MutexOwner {
        <<@Immutable>>
        +ownerId: String
        +acquiredAt: Long
        +ttlAt: Long
        +transitionAt: Long
        +isInTtl: Boolean
        +isInTransition: Boolean
        +hasOwner(): Boolean
        +isOwner(contenderId: String): Boolean
        +isInTtl(contenderId: String): Boolean
        +isInTransitionOf(contenderId: String): Boolean
    }

    class MutexOwnerEntity {
        +mutex: String
        +version: int
        +currentDbAt: Long
    }

    MutexOwner <|-- MutexOwnerEntity

MutexState

MutexState is a data class that carries a before/after pair of MutexOwner values. It captures ownership transitions and provides convenience predicates for determining what happened.

FieldTypeDescription
beforeMutexOwnerThe previous owner (or MutexOwner.NONE)
afterMutexOwnerThe current owner (or MutexOwner.NONE)

Key derived properties:

  • isChanged (line 36) — true when before.ownerId != after.ownerId.
  • isAcquired(contenderId) (line 38) — true when the state changed AND the given contender is the new owner.
  • isReleased(contenderId) (line 42) — true when the state changed AND the given contender was the previous owner.
  • isOwner(contenderId) (line 46) — delegates to after.isOwner().
  • isInTtl(contenderId) (line 50) — true when the contender is owner AND TTL has not expired.

The companion object provides MutexState.NONE (line 32), which pairs two MutexOwner.NONE values.

mermaid
stateDiagram-v2
    direction LR
    [*] --> NoOwner: MutexState.NONE
    NoOwner --> AcquiredByA: A acquires (before=NONE, after=A)
    AcquiredByA --> RenewedByA: A renews (before=A, after=A, isChanged=false)
    RenewedByA --> ReleasedByA: B acquires (before=A, after=B)
    ReleasedByA --> AcquiredByB: onReleased(A) dispatched
    AcquiredByB --> NoOwner: B releases (before=B, after=NONE)
    NoOwner --> [*]

Interfaces

MutexRetriever

MutexRetriever is the root callback interface. Every participant in mutex contention must implement it.

kotlin
interface MutexRetriever {
    val mutex: String
    fun notifyOwner(mutexState: MutexState)
}
  • mutex — the name of the distributed mutex resource.
  • notifyOwner() — invoked by the contention service whenever the owner state changes.

MutexContender

MutexContender extends MutexRetriever with an identity and specialized callbacks:

kotlin
interface MutexContender : MutexRetriever {
    val contenderId: String
    fun onAcquired(mutexState: MutexState)
    fun onReleased(mutexState: MutexState)
}

The default notifyOwner() implementation (lines 27-37) filters no-change transitions and routes the callback:

  1. If mutexState.isChanged is false, return immediately (renewal, not a real transition).
  2. If mutexState.isAcquired(contenderId) — call onAcquired().
  3. If mutexState.isReleased(contenderId) — call onReleased().
mermaid
flowchart TD
    A["notifyOwner(mutexState)"] --> B{"isChanged?"}
    B -->|No| Z["Return (renewal, no-op)"]
    B -->|Yes| C{"isAcquired(this)?"}
    C -->|Yes| D["onAcquired(mutexState)"]
    C -->|No| E{"isReleased(this)?"}
    E -->|Yes| F["onReleased(mutexState)"]
    E -->|No| Z

    style A fill:#2d333b,stroke:#6d5dfc,color:#e6edf3
    style B fill:#2d333b,stroke:#6d5dfc,color:#e6edf3
    style C fill:#2d333b,stroke:#6d5dfc,color:#e6edf3
    style D fill:#2d333b,stroke:#6d5dfc,color:#e6edf3
    style E fill:#2d333b,stroke:#6d5dfc,color:#e6edf3
    style F fill:#2d333b,stroke:#6d5dfc,color:#e6edf3
    style Z fill:#2d333b,stroke:#30363d,color:#e6edf3

MutexRetrievalService

MutexRetrievalService defines the lifecycle of a contention service:

kotlin
interface MutexRetrievalService : AutoCloseable {
    val status: Status
    val mutexState: MutexState
    val running: Boolean
    fun start()
    fun stop()
}

The Status enum tracks lifecycle phases:

StatusMeaning
INITIALNot yet started
STARTINGTransitioning to running
RUNNINGActively contending
STOPPINGShutting down

isActive (line 59) returns true for both STARTING and RUNNING.

MutexContendService

MutexContendService extends MutexRetrievalService and adds contender-specific queries:

kotlin
interface MutexContendService : MutexRetrievalService {
    val contender: MutexContender
    val isOwner: Boolean
    val isInTtl: Boolean
}
  • isOwner (line 36) — checks if the bound contender is the current owner.
  • isInTtl (line 38) — checks if the bound contender is owner AND TTL is still valid.

Factory Interfaces

MutexRetrievalServiceFactory

MutexRetrievalServiceFactory creates retrieval services (observation-only, no contention):

kotlin
interface MutexRetrievalServiceFactory {
    fun createMutexRetrievalService(retrievalListener: MutexRetriever): MutexRetrievalService
}

MutexContendServiceFactory

MutexContendServiceFactory creates contention services (full lock acquisition):

kotlin
interface MutexContendServiceFactory {
    fun createMutexContendService(mutexContender: MutexContender): MutexContendService
}

All three backend modules provide implementations of this interface.

Abstract Implementations

AbstractMutexRetrievalService

AbstractMutexRetrievalService is the base class for all retrieval services. It manages:

  • Status transitions — uses AtomicReferenceFieldUpdater (line 32) for lock-free CAS on the status field. start() requires INITIAL -> STARTING, and stop() requires RUNNING -> STOPPING.
  • Owner state — the mutexState field is @Volatile and updated in safeNotifyOwner().
  • Async notificationnotifyOwner(newOwner) dispatches via CompletableFuture.runAsync() on the handleExecutor, ensuring that slow callbacks never block the contention thread.
  • Template methods — subclasses implement startRetrieval() and stopRetrieval().
mermaid
sequenceDiagram
autonumber
    participant Caller as Caller
    participant ARS as AbstractMutexRetrievalService
    participant HE as handleExecutor
    participant R as MutexRetriever

    Caller->>ARS: start()
    ARS->>ARS: CAS(INITIAL -> STARTING)
    ARS->>ARS: startRetrieval()
    ARS->>ARS: set(RUNNING)

    Note over ARS: ... contention loop runs ...

    ARS->>ARS: newOwner detected
    ARS->>HE: CompletableFuture.runAsync(safeNotifyOwner)
    HE->>ARS: safeNotifyOwner(newOwner)
    ARS->>ARS: mutexState = MutexState(afterOwner, newOwner)
    ARS->>R: notifyOwner(newState)

    Caller->>ARS: stop()
    ARS->>ARS: CAS(RUNNING -> STOPPING)
    ARS->>ARS: stopRetrieval()
    ARS->>ARS: set(INITIAL)

AbstractMutexContendService

AbstractMutexContendService extends AbstractMutexRetrievalService and bridges the retrieval and contention layers:

kotlin
abstract class AbstractMutexContendService(
    override val contender: MutexContender,
    handleExecutor: Executor
) : AbstractMutexRetrievalService(contender, handleExecutor), MutexContendService {

    override fun startRetrieval() {
        resetOwner()
        startContend()
    }

    override fun stopRetrieval() {
        stopContend()
    }

    protected abstract fun startContend()
    protected abstract fun stopContend()
}

startRetrieval() resets the owner to NONE before calling startContend(), ensuring a clean slate. The two abstract methods (startContend / stopContend) are the extension points that JDBC, Redis, and Zookeeper backends implement.

AbstractMutexContender

AbstractMutexContender provides a concrete MutexContender base class with logging defaults:

  • Validates that both mutex and contenderId are non-blank in the init block (lines 30-32).
  • Defaults contenderId to ContenderIdGenerator.HOST.generate() (line 24).
  • Provides logging-only onAcquired() / onReleased() implementations.

SimbaLocker and AbstractScheduler.WorkContender both extend this class.

ContenderIdGenerator

ContenderIdGenerator is a strategy interface for generating unique contender identifiers. Two implementations are provided:

UUIDContenderIdGenerator

UUIDContenderIdGenerator generates a random UUID with dashes removed. Accessible via ContenderIdGenerator.UUID.

a1b2c3d4e5f6789012345678abcdef01

HostContenderIdGenerator

HostContenderIdGenerator generates IDs in the format {counter}:{processId}@{hostAddress}. Accessible via ContenderIdGenerator.HOST and used as the default in AbstractMutexContender.

0:12345@192.168.1.100
1:12345@192.168.1.100

The counter is an AtomicLong that increments per JVM, making IDs human-readable and traceable to a specific host and process. This is the preferred strategy for production deployments because it simplifies debugging ownership issues.

Ownership Notification Flow

The complete flow from backend detection to application callback:

mermaid
sequenceDiagram
autonumber
    participant BACKEND as Backend
    participant AMCS as AbstractMutexContendService
    participant HE as handleExecutor
    participant MC as MutexContender

    BACKEND->>AMCS: contends and obtains new MutexOwner
    AMCS->>AMCS: notifyOwner(newOwner)
    AMCS->>HE: CompletableFuture.runAsync(safeNotifyOwner)
    HE->>AMCS: safeNotifyOwner(newOwner)
    AMCS->>AMCS: state = MutexState(afterOwner, newOwner)
    AMCS->>AMCS: mutexState = state
    AMCS->>MC: notifyOwner(state)

    alt state.isAcquired(contenderId)
        MC->>MC: onAcquired(state)
    else state.isReleased(contenderId)
        MC->>MC: onReleased(state)
    end

Interface Relationship Diagram

The diagram below shows the full interface/implementation relationships in simba-core:

mermaid
flowchart TD
    subgraph Interfaces["Interfaces"]
        MR["MutexRetriever"]
        MC["MutexContender"]
        MRS["MutexRetrievalService"]
        MCS["MutexContendService"]
        MRSF["MutexRetrievalServiceFactory"]
        MCSF["MutexContendServiceFactory"]
        LOCKER["Locker"]
        CID["ContenderIdGenerator"]
    end

    subgraph Abstracts["Abstract Classes"]
        AMR["AbstractMutexRetrievalService"]
        AMC["AbstractMutexContendService"]
        AMCT["AbstractMutexContender"]
    end

    subgraph Values["Value Objects"]
        MO["MutexOwner"]
        MST["MutexState"]
        CP["ContendPeriod"]
    end

    MR -->|"extends"| MC
    MRS -->|"extends"| MCS
    MR -.->|"notifyOwner()"| MST
    MC -.->|"uses"| MO
    MRS -.->|"carries"| MST
    MCS -.->|"queries"| MO
    AMR -.->|"implements"| MRS
    AMC -.->|"extends"| AMR
    AMC -.->|"implements"| MCS
    AMCT -.->|"implements"| MC
    AMCT -.->|"extends"| MR

    style Interfaces fill:#2d333b,stroke:#6d5dfc,color:#e6edf3
    style Abstracts fill:#161b22,stroke:#30363d,color:#e6edf3
    style Values fill:#2d333b,stroke:#6d5dfc,color:#e6edf3

Summary

AbstractionTypeLocationPurpose
MutexOwnerValue objectcore/MutexOwner.ktImmutable snapshot of lock ownership
MutexStateValue objectcore/MutexState.ktBefore/after ownership transition
MutexRetrieverInterfacecore/MutexRetriever.ktCallback contract for ownership changes
MutexContenderInterfacecore/MutexContender.ktAdds identity + acquire/release hooks
MutexRetrievalServiceInterfacecore/MutexRetrievalService.ktService lifecycle + state access
MutexContendServiceInterfacecore/MutexContendService.ktContender-bound service with ownership queries
MutexContendServiceFactoryInterfacecore/MutexContendServiceFactory.ktCreates backend-specific contention services
AbstractMutexRetrievalServiceAbstract classcore/AbstractMutexRetrievalService.ktCAS-based lifecycle + async notification
AbstractMutexContendServiceAbstract classcore/AbstractMutexContendService.ktBridges retrieval to contention via template method
AbstractMutexContenderAbstract classcore/AbstractMutexContender.ktDefault contender with validation and logging
ContenderIdGeneratorInterfacecore/ContenderIdGenerator.ktStrategy for unique contender ID generation
ContendPeriodClasscore/ContendPeriod.ktComputes next scheduling delay based on ownership

Released under the Apache License 2.0.