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.
import klyn.process
import klyn.threading
| Need | Primary API |
|---|---|
| Run a command and collect its result | Process.execute() |
| Drive a shell, REPL, or debugger interactively | PseudoTerminal |
| Run Klyn work concurrently | Thread and Runnable |
| Protect a critical section | Mutex |
| Limit or signal concurrent work | Semaphore |
| Maintain a numeric counter atomically | atomic |
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.
ProcessResults exposes PID, exitCode, output, and
error. Always inspect exitCode; output text alone is not a success signal.
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.
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.
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")
A resource block calls close() automatically. Do not also call unlock() or
release() inside that block, or the primitive will be released twice.
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)
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.