ycl

Artifact [c7946d7776]
Login

Artifact [c7946d7776]

Artifact c7946d77761f8f66bd61cea12daa4a4d73ee5688:


#! /usr/bin/env tclsh

package require Thread

package require {ycl proc}
[yclprefix] proc alias [yclprefix]::proc::alias
alias [yclprefix]::proc::optswitch

package require {ycl list}
alias [yclprefix]::list::take


proc call {process cmd} {
	set call [namespace current]::processes::${process}::calls::[info cmdcount]
	namespace eval $call {}
	set varname ${call}::response
	set cmdprefix [::list ::apply [
		::list {coroutine process call args} {

		::tailcall $coroutine done $process $call
	} [namespace current]] [info coroutine] $process $call]
	set trace [::list $varname write $cmdprefix]
	set ${call}::trace $trace
	trace add variable {*}$trace
	set ${call}::caller [info coroutine]
	thread::send -async $process $cmd $varname
	yield
}


proc calls process {
	lsort -dictionary [namespace children processes::${process}::calls]
}


proc calls_interrupt {reason callers process status} {
	foreach caller $callers {
		$caller $reason $process $status
	}
}


proc cleanup {process reason status} {
	set callers [lmap call [calls $process] {
		namespace upvar $call caller caller
		set caller
	}]
	namespace delete processes::$process
	calls_interrupt $reason $callers $process $status
}


proc exited {process status} {
	cleanup $process exited $status
}


proc kill process {
	thread::release $process
	cleanup $process killed 127
}


proc list {} {
	lsort -dictionary [lmap process [namespace children processes] {
		namespace tail $process
	}]
}


variable doc::new {
	description {
		emulate a process a command in an interp in a separate thread
	}
}
proc new args {
	set kill 1
	while {[llength $args]} {
		take args opt
		optswitch $opt {
			config - cmd - kill - procname {
				take args $opt
			}
		}
	}
	set thread [thread::create]
	if {[info exists procname]} {
		upvar 1 $procname procvar
		set procvar $thread
	}
	thread::send $thread [string map [
		::list @controller@ [thread::id] @command@ "[
			::list [namespace current]] exited"] {

		::rename ::exit {}
		proc ::exit status {
			thread::send -async @controller@ "@command@ [thread::id] $status"
			thread::release
		}
	}]
	namespace eval processes::${thread}::calls {}
	if {[info exists config]} {
		call $thread $config
	}
	set res [call $thread $cmd]
	if {$kill} {
		kill $thread
	}
	return $res
}


proc yield {} {
	set args [lassign [::yieldto return -level 0 [info coroutine]] cstatus]
	switch $cstatus {
		done {
			lassign $args process call
			namespace upvar $call response response trace trace
			trace remove variable {*}$trace
			set res $response
			namespace delete $call
			return $res
		}
		exited - killed {
			lassign $args process status
			error [::list [::list process $cstatus] \
				process $process status $status]
		}
		default {
			error [::list {unknown call status} $cstatus]
		}
	}
}


namespace eval processes {}