################################################################################ # Module : palloc.tcl 2003-2007 # Date : 03.07.2007 # Purpose : Implements a persistent pool of handles. Originally developed for # the management of tcp ports in a given range across multiple pcs. # No precautions yet for keeping pool consistent. # Author : M.Hoffmann # Notes : - Could make use of tie, a db or bitstrings. # - A avail-query could be implemented (perhaps via statearray). # Wiki : https://wiki.tcl-lang.org/19673 # History : # 03072007 2.0 - everything rewritten using 'lock', partially incompatible api. # ################################################################################ package require lock ; # see https://wiki.tcl-lang.org/15173 package provide palloc 2.0 ; # namespace eval palloc { } #------------------------------------------------------------------------------- # -- init # Initialize a persistent pool of `poolSize` bytes in the file 'dbName'. Each # char position in the poolfile (later implementations may use individual bits) # represents a handle, where the char value '0' means 'free/available', and '1' # means 'used/not available'. The file must not exist (EXCL) and therefore can # no longer be resized by this method, compared to previous versions. Returns # an empty string or raises an error. Att: No precautions for conflicts here. # proc palloc::init {dbName poolSize} { set h [open $dbName {WRONLY CREAT EXCL}] puts -nonewline $h [string repeat 0 $poolSize] close $h return "" } #------------------------------------------------------------------------------- # --alloc # Abstraction layer upon lock::withLock, to retrieve 'count' free handles # (default count: 1) from the pool 'dbName', which have to exist (see 'init'). # 'timeout' is passed over via 'withLock' to 'acquireLock'. # Eventually returning less handles then requested, or an empty list if no more # handles are availabe at all. Attention: if called in a loop, competing callers # of 'alloc' will likely time out! Such a loop should contain sleeps or many # should be allocated with one call instead. # proc palloc::alloc {dbName {count 1} {timeout 1000}} { set res [list ] catch {lock::withLock { set h [open $dbName RDWR] seek $h 0 set pool [read $h] set free 0 while {$count > 0} { set free [string first "0" $pool $free] if {$free == -1} { break } lappend res $free set pool [string replace $pool $free $free "1"] incr count -1 incr free } if {[llength $res]} { # save the changes seek $h 0 puts -nonewline $h $pool } close $h } $timeout $dbName.lock} return $res } #------------------------------------------------------------------------------- # --free # Deallocating the 'handles', marking them as free in 'dbName'. # 'timeout' is passed over via 'withLock' to 'acquireLock'. # Returning the handles which are successfully freed. # proc palloc::free {dbName handles {timeout 1000}} { set res [list ] catch {lock::withLock { set h [open $dbName RDWR] seek $h 0 set pool [read $h] foreach hdl $handles { if {[string range $pool $hdl $hdl] == "1"} { lappend res $hdl set pool [string replace $pool $hdl $hdl "0"] } } if {[llength $res]} { # save the changes seek $h 0 puts -nonewline $h $pool } close $h } $timeout $dbName.lock} return $res }
# palloc_test1.tcl -- Testsuite 03.07.2007 M.Hoffmann # This does not test concurrend operations, see test2 for that. lappend auto_path [pwd] package require palloc 2.0 proc doTests {cmds} { foreach cmd $cmds { set command [lindex $cmd 0] set expectedResult [lindex $cmd 1] set comment [lindex $cmd 2] catch {uplevel $command} currentResult set failCount 0 if {$expectedResult != $currentResult} { set marker "***ERR***" incr failCount } else { set marker "ok" } puts "Command : $command" puts "Result : $currentResult" puts "Expected: $expectedResult" puts "Comment : $comment" puts $marker\n } puts [expr {$failCount > 0 ? "***TESTS FAILED!!!***" : "Tests passed"}] return [expr {$failCount != 0}] } catch {file delete pool.1} exit [doTests { {{palloc::init pool.1 500 } "" {} } {{palloc::init pool.1 250 } {couldn't open "pool.1": file already exists} {because of EXCL-flag with open, explicit delete required}} {{palloc::alloc pool.1 } 0 {} } {{palloc::alloc pool.1 } 1 {} } {{palloc::alloc pool.1 } 2 {} } {{palloc::alloc pool.1 } 3 {} } {{palloc::alloc pool.1 } 4 {} } {{palloc::free pool.1 4 5} 4 {Handle 5 not allocated} } {{palloc::alloc pool.1 } 4 {} } {{palloc::alloc pool.1 10 } {5 6 7 8 9 10 11 12 13 14} {} } {{palloc::alloc pool.1 } 15 {} } }]
# palloc_test2.tcl -- Concurrency-tests for palloc - 03.07.2007 M.Hoffmann # this calls palloc_test3.tcl multiple times after initializing a pool. lappend auto_path [pwd] package require palloc 2.0 package require bgexec; # see https://wiki.tcl-lang.org/12704 catch { file delete pool.2 palloc::init pool.2 500 } set pCount 0 proc cb {data} { puts $data } # ok, this is not an exactly parrallel start... # should be revised to provide true parallel execution start bgExec [list tclsh palloc_test3.tcl] cb pCount bgExec [list tclsh palloc_test3.tcl] cb pCount bgExec [list tclsh palloc_test3.tcl] cb pCount bgExec [list tclsh palloc_test3.tcl] cb pCount bgExec [list tclsh palloc_test3.tcl] cb pCount while {$pCount > 0} { vwait pCount }
# palloc_test2.tcl -- Concurrency-tests for palloc - 03.07.2007 M.Hoffmann # this calls palloc_test3.tcl multiple times after initializing a pool. lappend auto_path [pwd] package require palloc 2.0 package require bgexec; # see https://wiki.tcl-lang.org/12704 catch { file delete pool.2 palloc::init pool.2 500 } set pCount 0 proc cb {data} { puts $data } # ok, this is not an exactly parrallel start... # should be revised to provide true parallel execution start bgExec [list tclsh palloc_test3.tcl] cb pCount bgExec [list tclsh palloc_test3.tcl] cb pCount bgExec [list tclsh palloc_test3.tcl] cb pCount bgExec [list tclsh palloc_test3.tcl] cb pCount bgExec [list tclsh palloc_test3.tcl] cb pCount while {$pCount > 0} { vwait pCount }