Tcl Library Source Code

Check-in [bc65bd4004]
Login

Many hyperlinks are disabled.
Use anonymous login to enable hyperlinks.

Overview
Comment:Moved the cluster::sleep function to the cron module Added a coroutine aware function called "sleep" to the cron system. This allows either a routine or coroutine to sleep in a multi-tasking friendly way.
Timelines: family | ancestors | descendants | both | odie
Files: files | file ages | folders
SHA1: bc65bd40049902e9388d54f77b1eb4406e6e21be
User & Date: tne 2016-07-20 21:47:38
Context
2016-07-20
23:25
Removed the special case for coroutine sleeps check-in: 9d43ec95cf user: tne tags: odie
21:47
Moved the cluster::sleep function to the cron module Added a coroutine aware function called "sleep" to the cron system. This allows either a routine or coroutine to sleep in a multi-tasking friendly way. check-in: bc65bd4004 user: tne tags: odie
19:14
Moved the coroutine registration system from TOOL over to the cron module Refactored how the loop and panic alarm system works for timed events in tool/cron Bumped the versions for both cron and tool check-in: e6d8e5ee15 user: tne tags: odie
Changes
Hide Diffs Unified Diffs Ignore Whitespace Patch

Changes to modules/cron/cron.tcl.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
###
# 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)
package require coroutine

::namespace eval ::cron {}

proc ::cron::at args {
  switch [llength $args] {
    2 {
      variable processuid
      set process event#[incr processuid]








|







1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
###
# 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)
package require coroutine
package require dicttool
::namespace eval ::cron {}

proc ::cron::at args {
  switch [llength $args] {
    2 {
      variable processuid
      set process event#[incr processuid]
85
86
87
88
89
90
91



































92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109

110
111
112

113



114
115
116
117
118
119
120
  }

  lappend all_coroutines $coroutine
  lappend object_coroutines($objname) $coroutine
  set coroutine_object($coroutine) $objname
  return 0
}




































proc ::cron::coroutine_unregister {coroutine} {
  variable all_coroutines
  variable object_coroutines
  variable coroutine_object
  ldelete all_coroutines $coroutine
  if {[info exists coroutine_object($coroutine)]} {
    set objname $coroutine_object($coroutine)
    ldelete object_coroutines($objname) $coroutine
    unset coroutine_object($coroutine)
  }
}

proc ::cron::do_events {} {
  # Process coroutines
  variable all_coroutines
  variable coroutine_object
  variable coroutine_busy

  variable last_event
  set last_event [clock seconds]
  set count 0

  foreach coro $all_coroutines {



    if {![info exists coroutine_busy($coro)]} {
      set coroutine_busy($coro) 0
    }
    # Prevent a stuck coroutine from logjamming the entire event loop
    if {$coroutine_busy($coro)} continue
    set coroutine_busy($coro) 1
    if {[info command $coro] eq {}} {







>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>


















>



>

>
>
>







85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
  }

  lappend all_coroutines $coroutine
  lappend object_coroutines($objname) $coroutine
  set coroutine_object($coroutine) $objname
  return 0
}

proc ::cron::sleep_handle {ms} {
  set eventid [incr ::cron::eventcount]
  set var ::cron::event($eventid)
  set ${var} [expr {[clock milliseconds]+$ms}]
  return $var
}


proc ::cron::sleep ms {
  set coro [info coroutine]
  if {$coro eq {}} {
    set handle [sleep_handle $ms]
    ::cron::wake CRON_SLEEP
    while {[set $handle]>0} {
      vwait $handle
    }
    unset $handle
    return
  }
  variable all_coroutines
  variable coroutine_sleep
  if {$coro ni $all_coroutines} {
    coroutine_register {} $coro
  }
  if {![info exists coroutine_sleep($coro)]} {
    set coroutine_sleep($coro) [expr {[clock milliseconds]+$ms}]
    ::cron::wake COROUTINE_SLEEP
  }
  yield
  while {$coroutine_sleep($coro)>[clock milliseconds]} {
    yield
  }
  unset coroutine_sleep($coro)
}

proc ::cron::coroutine_unregister {coroutine} {
  variable all_coroutines
  variable object_coroutines
  variable coroutine_object
  ldelete all_coroutines $coroutine
  if {[info exists coroutine_object($coroutine)]} {
    set objname $coroutine_object($coroutine)
    ldelete object_coroutines($objname) $coroutine
    unset coroutine_object($coroutine)
  }
}

proc ::cron::do_events {} {
  # Process coroutines
  variable all_coroutines
  variable coroutine_object
  variable coroutine_busy
  variable coroutine_sleep
  variable last_event
  set last_event [clock seconds]
  set count 0
  set mnow [clock milliseconds]
  foreach coro $all_coroutines {
    if {[info exists coroutine_sleep($coro)]} {
      if {$coroutine_sleep($coro) > $mnow} continue
    }
    if {![info exists coroutine_busy($coro)]} {
      set coroutine_busy($coro) 0
    }
    # Prevent a stuck coroutine from logjamming the entire event loop
    if {$coroutine_busy($coro)} continue
    set coroutine_busy($coro) 1
    if {[info command $coro] eq {}} {
251
252
253
254
255
256
257



















258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275




276
277
278
279
280
281
282
        if {$scheduled < $nextevent} {
          set nexttask $process
          set nextevent $scheduled
        }
        set lastevent $now
      }
    }



















    set delay [expr {$nextevent-$now}]
    if {$delay < 0} {
      yield 0
    } else {
      if {$delay > 120} {
        set delay [expr {$delay-($delay % 60) + 60}]
      }
      yield $delay      
    }
  }
}

proc ::cron::wake {{who ???}} {
  ##
  # Only triggered by cron jobs kicking off other cron jobs within
  # the script body
  ##
  after cancel $::cron::next_event




  if {$who eq "PANIC"} {
    # Cron is overdue and may be stuck
    set ::cron::busy 0
    set ::cron::panic_event {}
  }
  if {$::cron::busy && $::cron::panic_event eq {}} {
    puts "BUSY..."







>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>


















>
>
>
>







291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
        if {$scheduled < $nextevent} {
          set nexttask $process
          set nextevent $scheduled
        }
        set lastevent $now
      }
    }
    set mnow [clock milliseconds]
    foreach {coro msec} [array get ::cron::coroutine_sleep] {
      set scheduled [expr $msec/1000]
      if {$scheduled<$nextevent} {
        set nexttask $coro
        set nextevent $scheduled
      }
    }
    foreach {eventid msec} [array get ::cron::event] {
      if {$msec < 0} continue
      if {$msec < $mnow} {
        set ::cron::event($eventid) -1
      }
      set scheduled [expr $msec/1000]
      if {$scheduled<$nextevent} {
        set nexttask "SLEEP $eventid"
        set nextevent $scheduled
      }
    }  
    set delay [expr {$nextevent-$now}]
    if {$delay < 0} {
      yield 0
    } else {
      if {$delay > 120} {
        set delay [expr {$delay-($delay % 60) + 60}]
      }
      yield $delay      
    }
  }
}

proc ::cron::wake {{who ???}} {
  ##
  # Only triggered by cron jobs kicking off other cron jobs within
  # the script body
  ##
  after cancel $::cron::next_event
  if {[info coroutine] ne {}} {
    set ::cron::next_event [after idle {::cron::wake IDLE}]
    return
  }
  if {$who eq "PANIC"} {
    # Cron is overdue and may be stuck
    set ::cron::busy 0
    set ::cron::panic_event {}
  }
  if {$::cron::busy && $::cron::panic_event eq {}} {
    puts "BUSY..."

Changes to modules/cron/cron.test.

11
12
13
14
15
16
17



18
19
20



21
22
23
24
25
26
27
    [file dirname [file dirname [file join [pwd] [info script]]]] \
    devtools testutilities.tcl]

package require tcltest
testsNeedTcl     8.6
testsNeedTcltest 1.0




testing {
    useLocal cron.tcl cron
}




set timecounter 0
::cron::every timecounter 1 {incr timecounter}
set now [clock seconds]

# Test at
set timerevent 0







>
>
>



>
>
>







11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
    [file dirname [file dirname [file join [pwd] [info script]]]] \
    devtools testutilities.tcl]

package require tcltest
testsNeedTcl     8.6
testsNeedTcltest 1.0

support {
    use dicttool/dicttool.tcl   dicttool
}
testing {
    useLocal cron.tcl cron
}
set ::cron::trace 0
# Sleep until the top of the second
::cron::sleep [expr {1000-[clock milliseconds]%1000}]

set timecounter 0
::cron::every timecounter 1 {incr timecounter}
set now [clock seconds]

# Test at
set timerevent 0
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
























82
83
84
85
86
87

88
89
90
91
92
93
94
95
} 2

###
# Confirm cancel works
::cron::cancel timecounter
set timecounterfinal $::timecounter

after 2000 {set pause 0}
vwait pause
test cron-1.6 {cron::cancel} {
  set ::timecounter
} $::timecounterfinal

###
# Test the new IN command
###
set ::inevent 0
cron::in 5 {set ::inevent 1}

test cron-1.7 {cron::in} {
  set ::inevent
} 0

after 6000 {set pause 0}
vwait pause
test cron-1.8 {cron::in} {
  set ::inevent
} 1

























::cron::in 5 {
  set ::cron::forever 0
test cron-1.9 {cron::main} {
  set ::cron::forever
} 0
}

::cron::main

# If we get to this test, mission successful
test cron-1.10 {cron::main} {
  return 1
} 1
testsuiteCleanup
return







|
<














|
|




>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>


|



>



|




59
60
61
62
63
64
65
66

67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
} 2

###
# Confirm cancel works
::cron::cancel timecounter
set timecounterfinal $::timecounter

::cron::sleep 2000

test cron-1.6 {cron::cancel} {
  set ::timecounter
} $::timecounterfinal

###
# Test the new IN command
###
set ::inevent 0
cron::in 5 {set ::inevent 1}

test cron-1.7 {cron::in} {
  set ::inevent
} 0

::cron::sleep 6000

test cron-1.8 {cron::in} {
  set ::inevent
} 1

proc my_coro {} {
  set ::my_coro_progress 0
  set ::my_coro_start [clock milliseconds]
  ::cron::sleep 1250
  set ::my_coro_end [clock milliseconds]
  set ::my_coro_progress 1
}
coroutine TESTCORO my_coro
::cron::coroutine_register {} TESTCORO
test cron-1.9 {cron::coroutine_register} {
  set ::my_coro_progress
} 0

::cron::sleep 2000

test cron-1.10 {cron::coroutine_register} {
  set ::my_coro_progress
} 1

puts "DELAY [expr {$::my_coro_end - $::my_coro_start}]"
test cron-1.11 {cron::coroutine_register} {
  expr {($::my_coro_end - $::my_coro_start) >= 1250}
} 1

::cron::in 5 {
  set ::cron::forever 0
test cron-1.12 {cron::main} {
  set ::cron::forever
} 0
}
::cron::wake TEST
::cron::main

# If we get to this test, mission successful
test cron-1.13 {cron::main} {
  return 1
} 1
testsuiteCleanup
return

Changes to modules/udpcluster/pkgIndex.tcl.

1
2
3
4
if {![package vsatisfies [package provide Tcl] 8.5]} {return}
# Backward compadible alias
package ifneeded nameserv::cluster 0.2.5   {package require udpcluster ; package provide nameserv::cluster 0.2.5}
package ifneeded udpcluster 0.3.1  [list source [file join $dir udpcluster.tcl]]



|
1
2
3
4
if {![package vsatisfies [package provide Tcl] 8.5]} {return}
# Backward compadible alias
package ifneeded nameserv::cluster 0.2.5   {package require udpcluster ; package provide nameserv::cluster 0.2.5}
package ifneeded udpcluster 0.3.2  [list source [file join $dir udpcluster.tcl]]

Changes to modules/udpcluster/udpcluster.tcl.

43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
      chan flush $s
      chan close $s
    }
  } error]} {
    set ::cluster::broadcast_sock {}
    if {$::cluster::config(debug)} {
      puts "Broadcast ERR: $error - Reopening Socket"
      ::cluster::sleep 2000
    } else {
      # Double the delay
      ::cluster::sleep 250
    }
  }
}

###
# topic: 963e24601d0dc61580c9727a74cdba67
###







|


|







43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
      chan flush $s
      chan close $s
    }
  } error]} {
    set ::cluster::broadcast_sock {}
    if {$::cluster::config(debug)} {
      puts "Broadcast ERR: $error - Reopening Socket"
      ::cron::sleep 2000
    } else {
      # Double the delay
      ::cron::sleep 250
    }
  }
}

###
# topic: 963e24601d0dc61580c9727a74cdba67
###
163
164
165
166
167
168
169




170
171
172
173
174
175
176
    } else {
      set directory_sock {}
      set directory_pid {}
    }
  }
  return $broadcast_sock
}





proc ::cluster::TCPAccept {sock host port} {
  chan configure $sock -translation {crlf crlf} -buffering line -blocking 1
  set packet [chan gets $sock]
  if {![string is ascii $packet]} return
  if {![::info complete $packet]} return
  if {[catch {Directory {*}$packet} reply errdat]} {







>
>
>
>







163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
    } else {
      set directory_sock {}
      set directory_pid {}
    }
  }
  return $broadcast_sock
}

proc ::cluster::sleep args {
  ::cron::sleep {*}$args
}

proc ::cluster::TCPAccept {sock host port} {
  chan configure $sock -translation {crlf crlf} -buffering line -blocking 1
  set packet [chan gets $sock]
  if {![string is ascii $packet]} return
  if {![::info complete $packet]} return
  if {[catch {Directory {*}$packet} reply errdat]} {
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
  }
  while 1 {
    if {$::cluster::ping_recv($rcpt)} break
    if {([clock seconds] - $starttime) > $timeout} {
      error "Could not locate $rcpt on the network"
    }
    broadcast PING $rcpt
    sleep $::cluster::config(ping_sleep)
  }
  if {[::info exists ptpdata($rcpt)]} {
    return [dict getnull $ptpdata($rcpt) ipaddr]
  }
}

proc ::cluster::publish {url infodict} {







|







351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
  }
  while 1 {
    if {$::cluster::ping_recv($rcpt)} break
    if {([clock seconds] - $starttime) > $timeout} {
      error "Could not locate $rcpt on the network"
    }
    broadcast PING $rcpt
    ::cron::sleep $::cluster::config(ping_sleep)
  }
  if {[::info exists ptpdata($rcpt)]} {
    return [dict getnull $ptpdata($rcpt) ipaddr]
  }
}

proc ::cluster::publish {url infodict} {
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
    return
  }
  if [catch {::comm::comm send -async $commid $command {*}$args} reply] {
    puts $stderr "ERR: SEND $service $reply"
  }
}

proc ::cluster::sleep_handle {ms} {
  set eventid [incr ::cluster::eventcount]
  set var ::cluster::event($eventid)
  set ${var} [list [clock seconds] [expr {[clock milliseconds]+$ms}]]
  after $ms [list set $var -1]
  return $var
}


proc ::cluster::sleep ms {
  set handle [sleep_handle $ms]
  vwait $handle
}

###
# topic: c8475e832c912e962f238c61580b669e
###
proc ::cluster::search pattern {
  _Winnow
  set result {}  
  variable ptpdata







<
<
<
<
<
<
<
<
<
<
<
<
<
<







528
529
530
531
532
533
534














535
536
537
538
539
540
541
    return
  }
  if [catch {::comm::comm send -async $commid $command {*}$args} reply] {
    puts $stderr "ERR: SEND $service $reply"
  }
}















###
# topic: c8475e832c912e962f238c61580b669e
###
proc ::cluster::search pattern {
  _Winnow
  set result {}  
  variable ptpdata
664
665
666
667
668
669
670
671
  # See: RFC3692 and http://www.iana.org
  variable discovery_group 224.0.0.200
  variable local_port {}
  variable local_macid [lindex [::nettool::mac_list] 0]
  variable local_pid   [::uuid::uuid generate]
}

package provide udpcluster 0.3.1







|
654
655
656
657
658
659
660
661
  # See: RFC3692 and http://www.iana.org
  variable discovery_group 224.0.0.200
  variable local_port {}
  variable local_macid [lindex [::nettool::mac_list] 0]
  variable local_pid   [::uuid::uuid generate]
}

package provide udpcluster 0.3.2