1 # vim: ft=tcl foldmethod=marker foldmarker=<<<,>>> ts=4 shiftwidth=4
3 # Expected Datasource schema:
5 # column 1: event_source_script
7 # event_source_script must define a proc get_events {start_s end_s}
8 # that returns a list of event descriptions, each a list of
9 # {epoc_seconds event_data}
10 # Example (generates hourly events on the hour):
12 # proc get_events {start_s end_s} {
14 # set s [expr {($start_s / 3600) * 3600}]
15 # while {$s < $end_s} {
16 # lappend events [list $s [list the_posted_event_for $s]]
23 # event_fired(script_id, event_data) - When a scheduled event fires
25 class tlc
::Scheduler {
26 inherit tlc
::Baselog tlc
::Handlers
34 variable window_days
1.0
36 # Test framework helpers
37 method _test_pending_events
{}
43 variable refresh_afterid
""
44 variable current_horizon
47 method process_script
{id script start_s end_s
}
48 method process_event
{event}
49 method setup_events
{}
50 method remove_ev_source
{id
}
51 method update_last_run
{{s now
}}
53 method get_last_run
{}
56 method new_item
{pool id data
}
57 method change_item
{pool id olddata newdata
}
58 method remove_item
{pool id olddata
}
60 method new_source
{id script
}
65 body tlc
::Scheduler::constructor {args
} { #<<<
67 package require
Tcl 8.5
74 foreach reqf
{events_ds
} {
75 if {![info exists
$reqf] ||
[set $reqf] == ""} {
76 error "Must set -$reqf"
80 if {![itcl
::is object
$events_ds] ||
![$events_ds isa tlc
::Datasource]} {
81 error "-events_ds must be a tlc::Datasource or descendant"
84 if {[$events_ds isa tlc
::DSchan] ||
[$events_ds isa tlc
::Datasource_filter]} {
85 log debug
"-events_ds is a DSchan or Datasource_filter"
86 $events_ds register_handler new_item
[code
$this new_item
]
87 $events_ds register_handler change_item
[code
$this change_item
]
88 $events_ds register_handler remove_item
[code
$this remove_item
]
96 body tlc
::Scheduler::destructor {} { #<<<
98 after cancel
$refresh_afterid; set refresh_afterid
""
100 foreach {s afterid
} [array get afterids
] {
101 after cancel
$afterid
102 array unset afterids
$s
106 [info exists events_ds
] &&
107 [itcl
::is object
$events_ds] &&
109 [$events_ds isa tlc
::DSchan] ||
110 [$events_ds isa tlc
::Datasource_filter]
113 $events_ds deregister_handler new_item
[code
$this new_item
]
114 $events_ds deregister_handler change_item
[code
$this change_item
]
115 $events_ds deregister_handler remove_item
[code
$this remove_item
]
116 $events_ds deregister_handler init
[code
$this setup_events
]
121 body tlc
::Scheduler::fire {s
} { #<<<
123 foreach id
[after info] {
124 lappend afterinfo
[list $id [after info $id]]
126 log debug
"\nposted for this slot: [llength $posted($s)] items: ([join $posted($s) |])\npending afters:\n\t[join $afterinfo \n\t]"
127 if {[info exists afterids
($s)]} {
128 after cancel
$afterids($s)
129 array unset afterids
$s
131 if {![info exists posted
($s)]} return
133 foreach event $posted($s) {
138 log
error "error processing event: $errmsg\n$::errorInfo"
142 array unset posted
$s
148 body tlc
::Scheduler::process_script {id script start_s end_s
} { #<<<
150 set interp [interp create
-safe]
152 $interp alias remove_ev_source
[code
$this remove_ev_source
$id]
156 if {[$interp eval {expr {
157 [info commands get_events
] == "get_events"
159 set events
[$interp eval [list get_events
$start_s $end_s]]
162 log
error "Event source $id does not define get_events proc"
166 log
error "Error initializing safe interpreter with script or calling get_events: $errmsg ($::errorCode)\n$::errorInfo"
169 foreach event $events {
170 lassign
$event s event_data
172 if {$s < $start_s ||
$s > $end_s} continue
174 lappend posted
($s) [list $id $event_data]
175 if {![info exists afterids
($s)]} {
176 set delta
[expr {$s - [now
]}]
180 log notice
"scheduling after for $delta seconds time (slot $s)"
181 set afterids
($s) [after [expr {$delta * 1000}] \
182 [code
$this fire
$s]]
187 interp delete
$interp
191 body tlc
::Scheduler::process_event {event} { #<<<
193 lassign
$event script_id event_data
194 invoke_handlers event_fired
$script_id $event_data
198 body tlc
::Scheduler::setup_events {} { #<<<
200 after cancel
$refresh_afterid; set refresh_afterid
""
202 set last_run
[get_last_run
]
204 set end_s
[expr {[now
] + int
($window_days * 86400)}]
205 set current_horizon
$end_s
207 set id_column
[$events_ds cget
-id_column]
208 foreach row
[$events_ds get_list
{}] {
209 set script_id
[lindex $row $id_column]
210 new_item foo
$script_id $row
213 set delta
[expr {($end_s - [now
]) * 1000}]
215 set refresh_afterid
[after $delta [code
$this refresh
]]
219 body tlc
::Scheduler::remove_ev_source {id
} { #<<<
223 foreach {s events
} [array get posted
] {
225 foreach event $events {
226 set ev_script_id
[lindex $event 2]
227 if {$ev_script_id != $id} {
228 lappend new_events
$event
232 if {[llength $new_events] != [llength $events]} {
233 if {[llength $new_events] == 0} {
235 after cancel
$afterid
236 array unset afterids
$s
237 array unset posted
$s
240 set posted
($s) $new_events
247 body tlc
::Scheduler::update_last_run {{s now
}} { #<<<
253 set fp
[open "last_run" w
]
259 body tlc
::Scheduler::refresh {} { #<<<
264 foreach {s afterid
} {
266 after cancel
$afterid
267 array unset afterids
$s
268 array unset posted
$s
275 body tlc
::Scheduler::get_last_run {} { #<<<
278 set fp
[open "last_run" r
]
282 set last_run
[read $fp]
286 if {![string is integer
-strict $last_run]} {
294 body tlc
::Scheduler::new_item {pool id data
} { #<<<
296 set script
[lindex $data $script_col]
297 new_source
$id $script
301 body tlc
::Scheduler::change_item {pool id olddata newdata
} { #<<<
302 set oldscript
[lindex $olddata 1]
303 set newscript
[lindex $newdata 1]
304 if {$oldscript == $newscript} return
307 new_item
$pool $id $newdata
311 body tlc
::Scheduler::remove_item {pool id olddata
} { #<<<
317 body tlc
::Scheduler::new_source {id script
} { #<<<
319 tlc
::assert {[info exists current_horizon
]} "current_horizon defined"
321 set end_s
$current_horizon
323 process_script
$id $script $start_s $end_s
327 body tlc
::Scheduler::now {} { #<<<
328 return [clock seconds
]
332 body tlc
::Scheduler::_test_pending_events {} { #<<<
335 foreach s
[lsort -integer -increasing [array names posted
]] {
336 lappend build
$s $posted($s)