Standard library Processes Concurrency

Processes and Concurrency

Klyn separates child-process execution, interactive pseudo-terminals, threads, and synchronization. Choose the smallest abstraction that matches the work instead of sharing mutable state by default.

Imports and Responsibilities
import klyn.process
import klyn.threading
NeedPrimary API
Run a command and collect its resultProcess.execute()
Drive a shell, REPL, or debugger interactivelyPseudoTerminal
Run Klyn work concurrentlyThread and Runnable
Protect a critical sectionMutex
Limit or signal concurrent workSemaphore
Maintain a numeric counter atomicallyatomic
Execute a Child Process

Pass the executable and every argument as separate list entries. The call is synchronous and returns only after the child exits. Standard output and standard error are drained concurrently, so a child producing a large amount on either stream cannot deadlock the other stream.

import klyn.process

result = Process.execute(["klyn", "--version"])

if result.exitCode != 0:
    throw Exception("klyn failed: " + result.error)

print(result.output)
print("child PID: " + result.PID)
print("current PID: " + Process.current.PID)

The optional second argument is written to the child's standard input. Arguments are passed as an argument vector; shell operators, wildcard expansion, and quoting are not interpreted implicitly. Start a shell explicitly only when shell syntax is actually required.

Captured result

ProcessResults exposes PID, exitCode, output, and error. Always inspect exitCode; output text alone is not a success signal.

Interactive Programs with PseudoTerminal

Redirected pipes are insufficient for programs that expect a terminal. PseudoTerminal creates a real TTY, supports incremental reads, accepts control sequences, and can be resized in character cells.

import klyn.process

terminal = PseudoTerminal(command="klyn", columns=100, rows=30)
try:
    terminal.write("print(6 * 7)\n")
    Thread.sleep(50)
    print(terminal.readAvailable())
    terminal.resize(120, 36)
    terminal.write("exit\n")
finally:
    terminal.close()

readAvailable() never waits for more data and returns an empty string when nothing is ready. Poll it from a worker or timer in GUI applications; do not block the event thread.

Start and Join Threads

A lambda with no parameters satisfies Runnable. Constructing a thread does not start it; call start(), then join() when the caller requires completion.

import klyn.threading

worker = Thread(
    lambda(): print("background work"),
    "report-worker"
)

worker.start()
worker.join()

assert not worker.isAlive
print(worker.id)
print(worker.state)

join(timeoutMillis) limits how long the caller waits; it does not terminate the worker. Thread.currentThread returns the logical thread currently executing, and Thread.activeCount includes the main thread.

Protect Shared State

Mutex.lock() returns an AutoClosable guard. Prefer the resource form so the lock is released when the block returns or throws.

import klyn.threading

lock = Mutex()
total = 0

try lock.lock():
    total += 10

A semaphore represents permits rather than ownership. It is useful for bounded parallelism and for one thread to signal another.

slots = Semaphore(4)

try slots.acquire():
    # At most four workers may execute this block concurrently.
    print("processing one item")
Do not mix guard styles

A resource block calls close() automatically. Do not also call unlock() or release() inside that block, or the primitive will be released twice.

Atomic Numeric Counters

The atomic type modifier provides atomic numeric updates without a separate lock. It is appropriate for counters, but a mutex is still required when several values form one invariant.

import klyn.threading

completed as atomic = 0
completed++
completed += 9

assert completed == 10
print(completed.value)
Cooperative Cancellation

interrupt() records a cancellation request; it does not forcibly stop native execution. Long-running code must check Thread.currentThread.isInterrupted at useful boundaries and leave cleanly. This preserves resource safety and avoids terminating a worker while it owns a lock.

def processNextBatch() as Void:
    Thread.sleep(10)

worker = Thread(lambda():
    while not Thread.currentThread.isInterrupted:
        processNextBatch()
)

worker.start()
# Later, from the controlling thread:
worker.interrupt()
worker.join()

Prefer interrupt() plus explicit coordination over the logical suspend(), resume(), and stop() methods. Shared-state correctness remains the application's responsibility.