summaryrefslogtreecommitdiffstats
path: root/tcllib/modules/cron/cron.tcl
diff options
context:
space:
mode:
Diffstat (limited to 'tcllib/modules/cron/cron.tcl')
-rw-r--r--tcllib/modules/cron/cron.tcl281
1 files changed, 281 insertions, 0 deletions
diff --git a/tcllib/modules/cron/cron.tcl b/tcllib/modules/cron/cron.tcl
new file mode 100644
index 0000000..f799664
--- /dev/null
+++ b/tcllib/modules/cron/cron.tcl
@@ -0,0 +1,281 @@
+###
+# This file implements a process table
+# Instead of having individual components try to maintain their own timers
+# we centrally manage how often tasks should be kicked off here.
+###
+#
+# Author: Sean Woods (for T&E Solutions)
+
+::namespace eval ::cron {}
+
+proc ::cron::at args {
+ switch [llength $args] {
+ 2 {
+ variable processuid
+ set process event#[incr processuid]
+ lassign $args timecode command
+ }
+ 3 {
+ lassign $args process timecode command
+ }
+ default {
+ error "Usage: ?process? timecode command"
+ }
+ }
+ variable processTable
+ if {[string is integer -strict $timecode]} {
+ set scheduled $timecode
+ } else {
+ set scheduled [clock scan $timecode]
+ }
+ set now [clock seconds]
+ set info [list process $process frequency 0 command $command scheduled $scheduled lastevent $now]
+ if ![info exists processTable($process)] {
+ lappend info lastrun 0 err 0 result {}
+ }
+ foreach {field value} $info {
+ dict set processTable($process) $field $value
+ }
+ ::cron::wake
+ return $process
+}
+
+proc ::cron::in args {
+ switch [llength $args] {
+ 2 {
+ variable processuid
+ set process event#[incr processuid]
+ lassign $args timecode command
+ }
+ 3 {
+ lassign $args process timecode command
+ }
+ default {
+ error "Usage: ?process? timecode command"
+ }
+ }
+ variable processTable
+ set now [clock seconds]
+ set scheduled [expr {int(ceil($timecode+$now))}]
+ set info [list process $process frequency 0 command $command scheduled $scheduled lastevent $now]
+ if ![info exists processTable($process)] {
+ lappend info lastrun 0 err 0 result {}
+ }
+ foreach {field value} $info {
+ dict set processTable($process) $field $value
+ }
+ ::cron::wake
+ return $process
+}
+
+###
+# topic: 0776dccd7e84530fa6412e507c02487c
+###
+proc ::cron::every {process frequency command} {
+ variable processTable
+ set now [clock seconds]
+ set info [list process $process frequency $frequency command $command scheduled [expr {$now + $frequency}] lastevent $now]
+ if ![info exists processTable($process)] {
+ lappend info lastrun 0 err 0 result {}
+ }
+ foreach {field value} $info {
+ dict set processTable($process) $field $value
+ }
+ ::cron::wake
+}
+
+proc ::cron::cancel {process} {
+ variable processTable
+ unset -nocomplain processTable($process)
+}
+
+###
+# topic: 97015814408714af539f35856f85bce6
+###
+proc ::cron::run process {
+ variable processTable
+ dict set processTable($process) lastrun 0
+}
+
+proc ::cron::doOneEvent task {
+ variable lock 1
+ variable processTable
+ set now [clock seconds]
+ dict with processTable($task) {
+ set err [catch {uplevel #0 $command} result]
+ if $err {
+ puts $result
+ }
+ }
+ set lock 0
+}
+
+###
+# topic: 1f8d4726623321acc311734c1dadcd8e
+# description:
+# Run through our process table and
+# kick off overdue tasks
+###
+proc ::cron::runProcesses {} {
+ variable processTable
+ set now [clock seconds]
+ ###
+ # Determine what tasks to run this timestep
+ ###
+ set tasks {}
+ set cancellist {}
+ foreach {process} [array names processTable] {
+ dict with processTable($process) {
+ if { $scheduled <= $now } {
+ lappend tasks $process
+ if { $frequency <= 0 } {
+ lappend cancellist $process
+ } else {
+ set scheduled [expr {$frequency + $lastrun}]
+ if { $scheduled <= $now } {
+ set scheduled [expr {$frequency + $now}]
+ }
+ }
+ set lastrun $now
+ }
+ set lastevent $now
+ }
+ }
+ foreach task $tasks {
+ doOneEvent $task
+ }
+ foreach {task} $cancellist {
+ unset -nocomplain processTable($task)
+ }
+}
+
+###
+# topic: 2f5a33d28948c4514764bd2f58b750fc
+# description:
+# Called once per second, and timed to ensure
+# we run in roughly realtime
+###
+proc ::cron::runTasks {} {
+ variable lastcall
+ after cancel $lastcall
+ ###
+ # Run the processes before we kick off another task...
+ ###
+ catch {runProcesses}
+ variable processTable
+ ###
+ # Look at our schedule and book the next timeslot
+ # or 15 minutes, whichever is sooner
+ ###
+ set now [clock seconds]
+ set nexttime [expr {$now - ($now % 900) + 900}]
+ foreach {process} [array names processTable] {
+ dict with processTable($process) {
+ if {$scheduled > $now && $scheduled < $nexttime} {
+ set nexttime $scheduled
+ }
+ }
+ }
+ ###
+ # Try to get the event to fire off on the border of the
+ # nearest second
+ ###
+ if { $nexttime > $now } {
+ set ctime [clock milliseconds]
+ set next [expr {($nexttime-$now)*1000-1000+($ctime % 1000)}]
+ } else {
+ set next 0
+ }
+ set lastcall [after $next [namespace current]::runTasks]
+}
+
+
+###
+# topic: 21de7bb8db019f3a2fd5a6ae9b38fd55
+# description:
+# Called once per second, and timed to ensure
+# we run in roughly realtime
+###
+proc ::cron::runTasksCoro {} {
+ variable lastcall
+ after cancel $lastcall
+ ###
+ # Do this forever
+ ###
+ variable processTable
+ variable processing
+ while 1 {
+ set lastevent 0
+ set nextevent 0
+ set now [clock seconds]
+ ###
+ # Determine what tasks to run this timestep
+ ###
+ set tasks {}
+ set cancellist {}
+ foreach {process} [lsort -dictionary [array names processTable]] {
+ dict with processTable($process) {
+ if { $scheduled <= $now } {
+ lappend tasks $process
+ if { $frequency <= 0 } {
+ lappend cancellist $process
+ } else {
+ set scheduled [expr {$frequency + $lastrun}]
+ if { $scheduled <= $now } {
+ set scheduled [expr {$frequency + $now}]
+ }
+ }
+ set lastrun $now
+ } else {
+ if {$nextevent==0 || $scheduled < $nextevent} {
+ set $nextevent $scheduled
+ }
+ }
+ set lastevent $now
+ }
+ }
+ foreach task $tasks {
+ doOneEvent $task
+ yield 0
+ }
+
+ foreach {task} $cancellist {
+ unset -nocomplain processTable($task)
+ }
+ if {$nextevent==0} {
+ # Wake me up in 5 minutes, just out of principle
+ yield 300
+ } else {
+ yield $nextevent
+ }
+ }
+}
+
+
+
+proc ::cron::wake {} {
+ variable lock
+ ##
+ # Only triggered by cron jobs kicking off other cron jobs within
+ # the script body
+ ##
+ if {$lock} return
+ ::cron::runTasks
+}
+
+###
+# topic: 4a891d0caabc6e25fbec9514ea8104dd
+# description:
+# This file implements a process table
+# Instead of having individual components try to maintain their own timers
+# we centrally manage how often tasks should be kicked off here.
+###
+namespace eval ::cron {
+ variable lastcall 0
+ variable processTable
+ variable lock 0
+}
+
+::cron::wake
+package provide cron 1.2.1
+