Learning Objectives

  • To be able to understand entities that have processes that interact

  • To be able model discrete-event dynamic systems using the process view

  • To be able build process view models using the KSL

Process Interaction

  • Processes associated with two (or more) different entities can interact during their execution by directly suspending and resuming each other.
  • The higher-level constructs (hold, signal, blocking queue, wait-for) are all built on the same lower-level functionality: suspend the process, then resume it.
    • Example: with wait-and-signal, the entity’s process is suspended on the signal; the signal resumes it from that suspension point.
  • The KSL implements these suspensions using the low-level suspend() function.
  • Once suspended, a process must be resumed — the entity’s resumeProcess() function provides this capability.
  • All implementations that use the process modeling constructs must sub-class ProcessModel.

Holding Entities: the HoldQueue Class

  • A HoldQueue holds an entity within a queue until it is removed.
  • Key rule: an entity cannot remove itself from the hold queue.
    • Some other logic (a scheduled event or another entity) must remove and resume it.
  • hold(queue) places the entity in the queue and suspends its process.
  • HoldQueue is a subclass of Queue, so waiting statistics are collected and it can be iterated and searched.
  • Removal options:
    • removeAllAndResume() — remove and resume all held entities
    • removeAndResume(entity) — remove and resume a specific entity
    • inherited Queue methods remove without resuming (you must then resume manually)

HoldQueue Example

class HoldQExample(parent: ModelElement) : ProcessModel(parent, null) {
    private val myHoldQueue: HoldQueue = HoldQueue(this, "hold")
    private val myEventActionOne = EventActionOne()

    private inner class Customer : Entity() {
        val holdProcess: KSLProcess = process() {
            hold(myHoldQueue)     // enter hold queue; process suspends
            delay(10.0)           // resumes here after being removed
            delay(20.0)
        }
    }

    override fun initialize() {
        activate(Customer().holdProcess)
        activate(Customer().holdProcess, 1.0)
        schedule(myEventActionOne, 5.0)   // release event at time 5.0
    }

    private inner class EventActionOne : EventAction<Nothing>() {
        override fun action(event: KSLEvent<Nothing>) {
            myHoldQueue.removeAllAndResume()   // remove & resume all held
        }
    }
}
  • Both customers are held right after activation; the event at time 5.0 removes and resumes them, and they continue their delays.

Signaling Held Entities: the Signal Class

  • The Signal class builds on HoldQueue: it uses a hold queue to hold entities until they are notified to move via the index of their rank in the queue.
  • waitFor(signal) — the entity waits (suspends) for the signal.
  • signal.signal(0) signals the first entity; signal.signal(0..4) signals a Kotlin range of ranks.
  • Contrast with HoldQueue: on a signal the entity is notified and removes itself — the user does not have to remove and resume it.
  • Entities that are not signaled stay suspended; the ProcessModel cleans up any still-suspended entities at the end of a replication.

Signal Example

class SignalExample(parent: ModelElement, name: String? = null) : ProcessModel(parent, name) {

    private val signal = Signal(this, "SignalExample")

    private inner class SignaledEntity : Entity() {
        val waitForSignalProcess: KSLProcess = process {
            waitFor(signal)        // suspend until signaled
            delay(5.0)
        }
    }

    override fun initialize() {
        for (i in 1..10) {
            activate(SignaledEntity().waitForSignalProcess)
        }
        schedule(this::signalEvent, 3.0)
    }

    private fun signalEvent(event: KSLEvent<Nothing>) {
        signal.signal(0..4)        // signal the first 5 waiting entities
    }
}
  • 10 entities wait at time 0; at time 3.0 the first 5 (ranks 0..4) are signaled and resume; the other 5 remain waiting.

Blocking Queues: Communication Between Processes

  • A BlockingQueue lets two processes communicate: one sends items, another receives them.
  • It can block on sending or on receiving:
    • receiving from an empty channel blocks until an item is inserted
    • sending to a full channel blocks until space is made
  • Implemented with three queues:
    • channelQ — the channel holding items placed for removal (optional capacity; default Int.MAX_VALUE)
    • senderQ — holds entities blocked because the channel is full
    • receiverQ — holds entities waiting for requested items
  • Receivers request a specific amount and wait until that amount is available.

BlockingQueue Example

// capacity limits the channel; omit for effectively infinite
val blockingQ: BlockingQueue<QObject> = BlockingQueue(this, capacity = 10)

private inner class Receiver : Entity() {
    val receiving: KSLProcess = process("receiving") {
        for (i in 1..15) {
            delay(1.0)
            waitForItems(blockingQ, 1)    // block until 1 item is available
            delay(5.0)
        }
    }
}

private inner class Sender : Entity() {
    val sending: KSLProcess = process("sending") {
        for (i in 1..15) {
            delay(5.0)
            val item = QObject()
            send(item, blockingQ)         // block if the channel is full
        }
    }
}
  • Normal Kotlin control structures (e.g., for loops) work inside a process.
  • The receiver blocks at waitForItems() until the sender’s send() makes an item available; both processes then continue.

Waiting for Another Entity’s Process

  • waitFor(process: KSLProcess) lets one entity start another process and wait until that process runs to completion (like “run blocking”).
  • The current process suspends; when the activated process completes, the waiting process is resumed.
suspend fun waitFor(
    process: KSLProcess,
    timeUntilActivation: Double = 0.0,
    priority: Int = KSLEvent.DEFAULT_PRIORITY,
    suspensionName: String? = null
)

waitFor Example

private inner class Customer : Entity() {
    val simpleProcess: KSLProcess = process {
        wip.increment()
        timeStamp = time
        use(worker, delayDuration = st)     // seize-delay-release combined
        tip.value = time - timeStamp
        wip.decrement()
    }

    val wfp = process {
        val c = Customer()
        waitFor(c.simpleProcess)   // start c's process; wait for it to finish
    }
}
  • use() is a convenience suspending function that combines seize(), delay(), and release().
  • Entity ID_1 activates ID_2’s simpleProcess, suspends until ID_2 completes, then continues.

Direct Process Interaction: Soccer Mom & Daughter

Consider modeling the interaction between a soccer mom and her daughter. The soccer mom has a daughter who plays forward on a soccer team. The game day proceeds as follows. First, the mom drives the daughter to the game. The drive takes 30 minutes. After arriving to the field, the daughter exits the mini-van, which takes 2 minutes. After the daughter exits the van, the mom departs to run some errands, meanwhile the daughter plays soccer. The mom’s errands take approximately 45 minutes. The soccer game takes approximately 60 minutes. If the mom returns from the errands before the game ends, the mom waits patiently to pick up her daughter to go home. If the game ends, before the mom returns, the daughter waits patiently for the mom to return. Once the mother and daughter are together, the daughter loads up her soccer gear and enters the van. This loading time takes 2 minutes. After all is loaded, the happy mom and daughter drive home, which takes 30 minutes.

Modeling It with the Suspension Class

  • Use instances of the Suspension class to capture each suspension point and to share state between the entities.
  • The mom needs three suspensions: daughter exiting the van, daughter still playing, daughter loading the van. The daughter needs one: waiting for mom to finish shopping.
  • The mom’s process creates and activate()s the daughter, holding a reference to her; the daughter is constructed with a reference to the mom — so each can reach the other’s public Suspension properties.
  • Core operations:
    • suspendFor(suspension) — suspend at a labeled point
    • resume(other.suspension) — resume the other entity at that point
    • suspension.isSuspended — test whether the other side is currently waiting

Soccer Mom: the Mother’s Process

private inner class Mother : Entity() {
    val daughterExiting = Suspension(name = "Suspend for daughter to exit van")
    val daughterPlaying = Suspension(name = "Suspend for daughter playing")
    val daughterLoading = Suspension(name = "Suspend for daughter entering van")

    val momProcess = process {
        delay(30.0)                          // drive to game
        val daughter = Daughter(this@Mother)
        activate(daughter.daughterProcess)
        suspendFor(daughterExiting)          // wait for daughter to exit van
        delay(45.0)                          // run errands
        if (daughter.motherShopping.isSuspended) {
            resume(daughter.motherShopping)  // daughter done playing: resume her
        } else {
            suspendFor(daughterPlaying)      // daughter still playing: wait
        }
        suspendFor(daughterLoading)          // wait for daughter to load van
        delay(30.0)                          // drive home
    }
}

Soccer Mom: the Daughter’s Process

private inner class Daughter(val mother: Mother) : Entity() {

    val motherShopping = Suspension("Suspend for mother shopping")

    val daughterProcess = process {
        delay(2.0)                           // exit the van
        resume(mother.daughterExiting)       // resume mom
        delay(60.0)                          // play soccer
        if (mother.daughterPlaying.isSuspended) {
            resume(mother.daughterPlaying)   // mom waiting: resume her
        } else {
            suspendFor(motherShopping)       // mom still shopping: wait
        }
        delay(2.0)                           // load gear into van
        resume(mother.daughterLoading)       // resume mom
    }
}
  • The Daughter holds a reference to mother, so it can check mother.daughterPlaying.isSuspended and resume(...) the mom’s suspensions.

Soccer Mom: the Resulting Interaction

  • Trace for the case where the mom’s errands (45 min) finish before the game (60 min):
0.0> starting mom = ID_1
30.0> mom = ID_1 arrived at game
30.0> mom = ID_1 suspending for daughter to exit van
30.0> starting daughter ID_2
32.0> daughter, ID_2, exited the van
32.0> daughter, ID_2, resuming mom
32.0> daughter, ID_2, starting playing
32.0> mom = ID_1 running errands...
77.0> mom = ID_1 completed errands
77.0> mom, ID_1, mom suspending because daughter is still playing
92.0> daughter, ID_2, finished playing
94.0> daughter, ID_2, entered van, resuming mom
94.0> mom = ID_1 driving home
124.0> mom = ID_1 arrived home
  • Mom suspends at 30 (daughter exiting), runs errands 32–77, then suspends until the daughter finishes at 92; they drive home “together” at 94.

Same Interaction, Other Constructs

  • The identical coordination can be built with HoldQueue or Signal instead of Suspension.
  • Trade-off: Suspension collects no waiting statistics; use HoldQueue / Signal when you need queue statistics.
  • HoldQueue version — four hold queues stand in for the suspension points; the mom checks whether the daughter is waiting on her:
if (waitForMomToShopQ.contains(daughter)) {
    waitForMomToShopQ.removeAndResume(daughter)  // resume the daughter
} else {
    hold(waitForDaughterToPlayQ)                 // else wait for her to finish
}
  • This low-level coordination demands careful attention to shared state and is error-prone.

Blockages: a Preferred Alternative

  • A blockage is like a semaphore or lock on part of a process — a gate that makes other entities wait until it is cleared.
  • startBlockage(b)clearBlockage(b) mark the fenced region; another entity calls waitFor(b) to wait for it.
  • Rules: only the entity that created a blockage may start/clear it, and a started blockage must be cleared before the process ends.
  • BlockingActivity wraps a delay in a blockage; perform(activity) runs it, so you can’t forget to clear it.
private inner class Mom : Entity() {
    val shopping = BlockingActivity(45.0)      // a delay wrapped in a blockage
    val momProcess = process {
        delay(30.0)
        val daughter = Daughter(this@Mom)
        activate(daughter.daughterProcess)
        waitFor(daughter.unloading)   // suspend until daughter clears it
        perform(shopping)             // start blockage, delay, clear blockage
        waitFor(daughter.playing)
        waitFor(daughter.loading)
        delay(30.0)
    }
}
  • The daughter uses perform(unloading), perform(playing), waitFor(mom.shopping), perform(loading).
  • Related helpers: BlockingResourceUsage, BlockingResourcePoolUsage, BlockingMovement.

Summary of New Concepts

  • Process interaction: entities’ processes coordinate by suspending and resuming each other, built on the low-level suspend() / resumeProcess().
  • HoldQueue — holds (suspends) entities until removed; removeAllAndResume() / removeAndResume(entity). An entity cannot remove itself.
  • Signal — builds on HoldQueue; waitFor(signal) waits, signal.signal(0..4) releases entities by rank, and they remove themselves.
  • BlockingQueue — communication between two processes; send() blocks when full, waitForItems() blocks when empty (channelQ / senderQ / receiverQ).
  • waitFor(process) — start another process and suspend until it completes.
  • Suspension — direct suspend/resume via suspendFor() / resume() / isSuspended (no queue statistics).
  • Blockage / BlockingActivity — a gate (startBlockage/clearBlockage, or perform()); preferred over raw Suspension.
  • Implement process interaction by sub-classing ProcessModel and defining processes on sub-classes of Entity.
⌂ Index