Skip to content

Core Interfaces

The core contention protocol is defined by a small set of interfaces and abstract classes in the me.ahoo.simba.core package. This page documents each type with method signatures, relationships, and usage examples.

Class Diagram

mermaid
classDiagram
    class MutexRetriever {
        <<interface>>
        +mutex: String
        +notifyOwner(mutexState: MutexState)
    }
    class MutexContender {
        <<interface>>
        +contenderId: String
        +onAcquired(mutexState: MutexState)
        +onReleased(mutexState: MutexState)
    }
    class MutexRetrievalService {
        <<interface>>
        +status: Status
        +retriever: MutexRetriever
        +mutex: String
        +mutexState: MutexState
        +beforeOwner: MutexOwner
        +afterOwner: MutexOwner
        +hasOwner(): Boolean
        +running: Boolean
        +start()
        +stop()
        +close()
    }
    class MutexContendService {
        <<interface>>
        +contender: MutexContender
        +contenderId: String
        +isOwner: Boolean
        +isInTtl: Boolean
    }
    class MutexRetrievalServiceFactory {
        <<interface>>
        +createMutexRetrievalService(retrievalListener: MutexRetriever): MutexRetrievalService
    }
    class MutexContendServiceFactory {
        <<interface>>
        +createMutexContendService(mutexContender: MutexContender): MutexContendService
    }
    class MutexOwner {
        +ownerId: String
        +acquiredAt: Long
        +ttlAt: Long
        +transitionAt: Long
        +isOwner(contenderId: String): Boolean
        +isInTtl: Boolean
        +isInTransition: Boolean
        +hasOwner(): Boolean
    }
    class MutexState {
        +before: MutexOwner
        +after: MutexOwner
        +isChanged: Boolean
        +isAcquired(contenderId: String): Boolean
        +isReleased(contenderId: String): Boolean
        +isOwner(contenderId: String): Boolean
    }

    MutexContender --|> MutexRetriever : extends
    MutexContendService --|> MutexRetrievalService : extends
    MutexState *-- MutexOwner : before
    MutexState *-- MutexOwner : after
    MutexRetrievalService --> MutexRetriever : retriever
    MutexContendService --> MutexContender : contender

MutexRetriever

The most minimal contract in the contention protocol. Any object that wants to participate in mutex contention must implement this interface.

Source: simba-core/.../MutexRetriever.kt:20

kotlin
interface MutexRetriever {
    val mutex: String
    fun notifyOwner(mutexState: MutexState)
}
MethodReturnDescription
mutexStringThe logical name of the mutex resource. Must be non-blank.
notifyOwner(mutexState)voidCalled by the retrieval service whenever the mutex owner changes. The MutexState contains the previous and current owner.

MutexContender

Extends MutexRetriever with a contender identity and lifecycle callbacks. This is the primary interface application code implements.

Source: simba-core/.../MutexContender.kt:20

kotlin
interface MutexContender : MutexRetriever {
    val contenderId: String
    fun onAcquired(mutexState: MutexState)
    fun onReleased(mutexState: MutexState)
}
MethodReturnDescription
contenderIdStringUnique identifier for this contender instance. Typically generated by ContenderIdGenerator.
onAcquired(mutexState)voidCalled when this contender becomes the mutex owner. Use this to start leader-only work.
onReleased(mutexState)voidCalled when this contender loses the mutex (either due to TTL expiry, explicit stop, or another contender taking over).
notifyOwner(mutexState)voidDefault implementation dispatches to onAcquired/onReleased based on change detection.

The default notifyOwner implementation inspects the MutexState:

kotlin
override fun notifyOwner(mutexState: MutexState) {
    if (!mutexState.isChanged) return
    if (mutexState.isAcquired(contenderId)) onAcquired(mutexState)
    if (mutexState.isReleased(contenderId)) onReleased(mutexState)
}

MutexRetrievalService

The lifecycle-managed retrieval service. Provides start/stop semantics and exposes the current mutex state.

Source: simba-core/.../MutexRetrievalService.kt:20

kotlin
interface MutexRetrievalService : AutoCloseable {
    val status: Status
    val retriever: MutexRetriever
    val mutex: String
    val mutexState: MutexState
    val beforeOwner: MutexOwner
    val afterOwner: MutexOwner
    fun hasOwner(): Boolean
    val running: Boolean
    fun start()
    fun stop()
}
Method / PropertyReturnDescription
statusStatusCurrent lifecycle status: INITIAL, STARTING, RUNNING, STOPPING
retrieverMutexRetrieverThe retriever this service is bound to
mutexStringThe mutex name, delegated from retriever.mutex
mutexStateMutexStateCurrent state snapshot (weakly consistent with backend)
beforeOwnerMutexOwnerThe previous owner from the last transition
afterOwnerMutexOwnerThe current owner from the last transition
hasOwner()Booleantrue if afterOwner !== MutexOwner.NONE
runningBooleantrue if status is STARTING or RUNNING
start()voidTransitions from INITIAL to RUNNING. Throws if not in INITIAL.
stop()voidTransitions from RUNNING to INITIAL. Throws if not in RUNNING.
close()voidDelegates to stop()

Status Enum

kotlin
enum class Status {
    INITIAL,   // Not started
    STARTING,  // In the process of starting
    RUNNING,   // Actively contending
    STOPPING;  // In the process of stopping

    val isActive: Boolean
        get() = this == STARTING || this == RUNNING
}
mermaid
stateDiagram-v2
    [*] --> INITIAL
    INITIAL --> STARTING : start()
    STARTING --> RUNNING : contend succeeds
    RUNNING --> STOPPING : stop()
    STOPPING --> INITIAL : cleanup
    STARTING --> INITIAL : error

MutexContendService

Extends MutexRetrievalService with contender-specific ownership queries.

Source: simba-core/.../MutexContendService.kt:20

kotlin
interface MutexContendService : MutexRetrievalService {
    val contender: MutexContender
    val contenderId: String
    val isOwner: Boolean
    val isInTtl: Boolean
}
Method / PropertyReturnDescription
contenderMutexContenderThe bound contender instance
contenderIdStringDelegates to contender.contenderId
isOwnerBooleantrue if afterOwner.ownerId == contenderId
isInTtlBooleantrue if the contender is the owner AND the lock has not expired its TTL

MutexRetrievalServiceFactory

Source: simba-core/.../MutexRetrievalServiceFactory.kt:20

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

MutexContendServiceFactory

Source: simba-core/.../MutexContendServiceFactory.kt:20

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

Each backend module provides a concrete implementation:

  • JdbcMutexContendServiceFactory in simba-jdbc
  • SpringRedisMutexContendServiceFactory in simba-spring-redis
  • ZookeeperMutexContendServiceFactory in simba-zookeeper

AbstractMutexContender

A convenience base class that provides default logging for onAcquired and onReleased.

Source: simba-core/.../AbstractMutexContender.kt:22

kotlin
abstract class AbstractMutexContender(
    final override val mutex: String,
    final override val contenderId: String = ContenderIdGenerator.HOST.generate()
) : MutexContender
ParameterDefaultDescription
mutex--The mutex resource name. Must be non-blank.
contenderIdContenderIdGenerator.HOST.generate()Auto-generated counter:pid@host format

AbstractMutexRetrievalService

Template method base class for retrieval services. Manages the Status state machine using AtomicReferenceFieldUpdater for thread-safe transitions.

Source: simba-core/.../AbstractMutexRetrievalService.kt:26

kotlin
abstract class AbstractMutexRetrievalService(
    override val retriever: MutexRetriever,
    protected val handleExecutor: Executor
) : MutexRetrievalService

Key behaviors:

  • start() -- CAS from INITIAL to STARTING, calls startRetrieval(), sets RUNNING
  • stop() -- CAS from RUNNING to STOPPING, calls stopRetrieval(), sets INITIAL
  • notifyOwner(newOwner) -- dispatches safeNotifyOwner on handleExecutor, which updates mutexState and calls retriever.notifyOwner

AbstractMutexContendService

Bridges AbstractMutexRetrievalService with backend-specific contention logic.

Source: simba-core/.../AbstractMutexContendService.kt:22

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()
}

Backend modules (simba-jdbc, simba-spring-redis, simba-zookeeper) extend this class and implement startContend() and stopContend().

MutexOwner

An immutable value object that represents a snapshot of mutex ownership at a point in time.

Source: simba-core/.../MutexOwner.kt:23

kotlin
@Immutable
open class MutexOwner(
    val ownerId: String,
    val acquiredAt: Long = System.currentTimeMillis(),
    val ttlAt: Long = Long.MAX_VALUE,
    val transitionAt: Long = Long.MAX_VALUE
)
PropertyTypeDescription
ownerIdStringThe contenderId of the current owner. Empty string (NONE_OWNER_ID) means no owner.
acquiredAtLongTimestamp (epoch millis) when the lock was acquired
ttlAtLongTimestamp when the TTL expires. After this, the owner should renew or another contender may take over.
transitionAtLongEnd of the transition/grace period. During this window the current owner can preferentially renew.
MethodReturnDescription
isOwner(contenderId)BooleanChecks if the given ID matches ownerId
isInTtlBooleantrue if ttlAt > System.currentTimeMillis()
isInTtl(contenderId)Booleantrue if is owner AND within TTL
isInTransitionBooleantrue if transitionAt >= System.currentTimeMillis()
hasOwner()Booleantrue if transitionAt >= System.currentTimeMillis()
MutexOwner.NONEMutexOwnerSentinel: ownerId = "", all timestamps 0

MutexState

Represents a transition between two owners. Used as the argument to notifyOwner.

Source: simba-core/.../MutexState.kt:20

kotlin
data class MutexState(
    val before: MutexOwner,
    val after: MutexOwner
)
MethodReturnDescription
isChangedBooleantrue if before.ownerId != after.ownerId
isAcquired(contenderId)BooleanisChanged && after.isOwner(contenderId)
isReleased(contenderId)BooleanisChanged && before.isOwner(contenderId)
isOwner(contenderId)Booleanafter.isOwner(contenderId)
MutexState.NONEMutexStateMutexState(MutexOwner.NONE, MutexOwner.NONE)

ContendPeriod

Computes the next scheduling delay for the contention loop. Owners renew before TTL; non-owners wait with jitter.

Source: simba-core/.../ContendPeriod.kt:22

kotlin
class ContendPeriod(private val contenderId: String) {
    fun ensureNextDelay(mutexOwner: MutexOwner): Long
    fun nextDelay(mutexOwner: MutexOwner): Long
}
ScenarioDelay Calculation
Current ownerttlAt - now (renew just before TTL expiry)
Non-owner (no transition)transitionAt - now + random(0, 1000)
Non-owner (with transition)transitionAt - now + random(-200, 1000)

The random jitter (-200ms to +1000ms) prevents thundering herd when multiple contenders retry simultaneously.

mermaid
sequenceDiagram
autonumber
    participant Owner as Current Owner
    participant Service as ContendService
    participant Backend as Backend

    Note over Owner: TTL approaching
    Service->>Backend: acquire / guard
    Backend-->>Service: MutexOwner (isOwner=true)
    Service->>Service: nextDelay = ttlAt - now
    Service->>Service: schedule next contend

    Note over Service,Backend: Owner renews before TTL expires

    Note over Owner: TTL expired, owner did not renew
    Backend-->>Service: MutexOwner (different owner)
    Service->>Service: nextDelay = transitionAt - now + jitter
    Service->>Service: schedule retry with jitter

ContenderIdGenerator

Generates unique contender identifiers.

Source: simba-core/.../ContenderIdGenerator.kt:24

kotlin
interface ContenderIdGenerator {
    fun generate(): String
}
ImplementationPatternExample
ContenderIdGenerator.HOST (default){counter}:{pid}@{host}0:12345@192.168.1.10
ContenderIdGenerator.UUIDUUID without hyphensa1b2c3d4e5f6...

The HOST strategy is preferred because it is human-readable and helps with debugging in logs. The counter is a process-local AtomicLong that increments per contender creation.

Usage Example

kotlin
import me.ahoo.simba.core.*

class MyService : AbstractMutexContender("order-settlement") {

    override fun onAcquired(mutexState: MutexState) {
        super.onAcquired(mutexState)
        println("[$contenderId] acquired leadership for ${mutex}")
        startSettlementTask()
    }

    override fun onReleased(mutexState: MutexState) {
        super.onReleased(mutexState)
        println("[$contenderId] lost leadership for ${mutex}")
        stopSettlementTask()
    }

    private fun startSettlementTask() { /* ... */ }
    private fun stopSettlementTask() { /* ... */ }
}

// Wire it up
val factory: MutexContendServiceFactory = JdbcMutexContendServiceFactory(
    mutexOwnerRepository = repository,
    initialDelay = Duration.ZERO,
    ttl = Duration.ofSeconds(10),
    transition = Duration.ofSeconds(6)
)

val service = factory.createMutexContendService(MyService())
service.start()

Relationship Summary

mermaid
graph LR
    subgraph sg_1 ["Abstraction Hierarchy"]

        R["MutexRetriever"] -->|"extended by"| C["MutexContender"]
        RS["MutexRetrievalService"] -->|"extended by"| CS["MutexContendService"]
        ARS["AbstractMutexRetrievalService"] -->|"extended by"| ACS["AbstractMutexContendService"]
    end
    subgraph sg_2 ["Factory Pattern"]

        RF["MutexRetrievalServiceFactory"] -->|"creates"| RS
        CF["MutexContendServiceFactory"] -->|"creates"| CS
    end
    subgraph sg_3 ["Template Method"]

        ACS -->|"implements"| CS
        ARS -->|"implements"| RS
        ACS -.->|"calls"| SC["startContend()"]
        ACS -.->|"calls"| STC["stopContend()"]
    end

    style R fill:#2d333b,stroke:#6d5dfc,color:#e6edf3
    style C fill:#2d333b,stroke:#6d5dfc,color:#e6edf3
    style RS fill:#2d333b,stroke:#6d5dfc,color:#e6edf3
    style CS fill:#2d333b,stroke:#6d5dfc,color:#e6edf3
    style ARS fill:#2d333b,stroke:#6d5dfc,color:#e6edf3
    style ACS fill:#2d333b,stroke:#6d5dfc,color:#e6edf3
    style RF fill:#2d333b,stroke:#6d5dfc,color:#e6edf3
    style CF fill:#2d333b,stroke:#6d5dfc,color:#e6edf3
    style SC fill:#2d333b,stroke:#6d5dfc,color:#e6edf3
    style STC fill:#2d333b,stroke:#6d5dfc,color:#e6edf3

See Also

Released under the Apache License 2.0.