Tcl Source Code

Check-in [12d1d63e18]
Login

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

Overview
Comment:after at: added simple workaround for absolute timers/sleep ("after at real-time"): because we use monotonic time in all wait functions, so to avoid too long wait by the absolute timers (to be able to trigger it) if time jumped to the expected absolute time, just let block for maximal 1 second if absolute timers available.

test-cases: time-jumps (TIP #302) test covered now. Note: on some platforms it is only possible if the user has corresponding privileges to change system date and time. Ex.: sudo LD_LIBRARY_PATH=. ./tclsh ../tests/timer.test -match timer-20.*

Downloads: Tarball | ZIP archive | SQL archive
Timelines: family | ancestors | descendants | both | sebres-8-5-event-perf-branch
Files: files | file ages | folders
SHA1: 12d1d63e1836836b9ab87a9171ed0a9fa6a28796
User & Date: sebres 2017-07-03 13:32:24
Context
2017-07-03
13:32
cleanup... check-in: 059712404c user: sebres tags: sebres-8-5-event-perf-branch
13:32
after at: added simple workaround for absolute timers/sleep ("after at real-time"): because we use m... check-in: 12d1d63e18 user: sebres tags: sebres-8-5-event-perf-branch
13:32
code review and small optimizations check-in: 3fc8da2b5f user: sebres tags: sebres-8-5-event-perf-branch
Changes
Hide Diffs Unified Diffs Ignore Whitespace Patch

Changes to generic/tclTimer.c.

939
940
941
942
943
944
945












946



947
948
949
950
951
952
953
954
955
	#ifdef TMR_RES_TOLERANCE
	    /* consider timer resolution tolerance (avoid busy wait) */
	    timeOffs -= ((timeOffs <= 1000000) ? timeOffs : 1000000) *
				TMR_RES_TOLERANCE / 100;
	#endif

	if (timeOffs > 0) {












	    blockTime.sec = (long) (timeOffs / 1000000);



	    blockTime.usec = (unsigned long) (timeOffs % 1000000);

	} else {
	    blockTime.sec = 0;
	    blockTime.usec = 0;
	}
	
    } else {
	return;







>
>
>
>
>
>
>
>
>
>
>
>
|
>
>
>
|
|







939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
	#ifdef TMR_RES_TOLERANCE
	    /* consider timer resolution tolerance (avoid busy wait) */
	    timeOffs -= ((timeOffs <= 1000000) ? timeOffs : 1000000) *
				TMR_RES_TOLERANCE / 100;
	#endif

	if (timeOffs > 0) {
	    blockTime.sec = 0;
	    if (timeOffs >= 1000000) {
		/*
		 * Note we use monotonic time by all wait functions, so to
		 * avoid too long wait by the absolute timers (to be able
		 * to trigger it) if time jumped to the expected time, just
		 * let block for maximal 1s if absolute timers available.
		 */
		if (tsdPtr->absTimerList) {
		    /* we've some absolute timers - won't wait longer as 1s. */
		    timeOffs = 1000000;
		}
		blockTime.sec = (long) (timeOffs / 1000000);
		blockTime.usec = (unsigned long)(timeOffs % 1000000);
	    } else {
		blockTime.sec = 0;
		blockTime.usec = (unsigned long)timeOffs;
	    }
	} else {
	    blockTime.sec = 0;
	    blockTime.usec = 0;
	}
	
    } else {
	return;
1788
1789
1790
1791
1792
1793
1794








1795
1796
1797
1798
1799
1800
1801
	) {
	    iPtr->limit.granularityTicker = 0;
	    if (Tcl_LimitCheck(interp) != TCL_OK) {
		return TCL_ERROR;
	    }
	}
	diff = endTime - now;








	if (iPtr->limit.timeEvent == NULL || diff < limOffs) {
	    if (diff > 0) {
		TclpUSleep(diff);
		if (!absolute) {
		    now = TclpGetUTimeMonotonic();
		} else {
		    now = TclpGetMicroseconds();







>
>
>
>
>
>
>
>







1803
1804
1805
1806
1807
1808
1809
1810
1811
1812
1813
1814
1815
1816
1817
1818
1819
1820
1821
1822
1823
1824
	) {
	    iPtr->limit.granularityTicker = 0;
	    if (Tcl_LimitCheck(interp) != TCL_OK) {
		return TCL_ERROR;
	    }
	}
	diff = endTime - now;
	if (absolute && diff >= 1000000) {
	    /*
	     * Note by absolute sleep we should avoid too long waits, to be
	     * able to process further if time jumped to the expected time, so
	     * just let wait maximal 1 second.
	     */
	    diff = 1000000;
	}
	if (iPtr->limit.timeEvent == NULL || diff < limOffs) {
	    if (diff > 0) {
		TclpUSleep(diff);
		if (!absolute) {
		    now = TclpGetUTimeMonotonic();
		} else {
		    now = TclpGetMicroseconds();

Changes to tests/timer.test.

576
577
578
579
580
581
582














































































































































































583
584
585
586
587
588
589
	    after $l [list lappend l "ev:$l"]
	}
	set l {}
	vwait done
	set l
    } \
    -result {ev:0 ev:0.1 ev:0.25 ev:0.5 ev:0.75 ev:1}















































































































































































# cleanup
::tcltest::cleanupTests
return

# Local Variables:
# mode: tcl







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







576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
	    after $l [list lappend l "ev:$l"]
	}
	set l {}
	vwait done
	set l
    } \
    -result {ev:0 ev:0.1 ev:0.25 ev:0.5 ev:0.75 ev:1}

# -----------------------------------------------------------------------------
# 
# timer-20.x --
# 
#    Following test-cases cover event-driven functionality during time-jump's
#
#    Note: on some platforms it is only possible if the user has corresponding
#          privileges to change system date and time.
#
# Ex.:  sudo LD_LIBRARY_PATH=. ./tclsh ../tests/timer.test -match timer-20.*
# -----------------------------------------------------------------------------

proc timejump {args} {
  set tnow [clock clicks]
  set tm [clock format [clock add [clock seconds] {*}$args] -format %H:%M:%S]
  #puts -nonewline "***[format \[%04X\] [pid]]*** jump to $tm ($args) "
  if {$::tcl_platform(platform) eq "windows"} {
    exec $::env(COMSPEC) /c time $tm
  } else {
    exec date +%T -s $tm
  }
  #puts "***[format \[%04X\] [pid]]*** [clock format [clock seconds] -format {%Y-%m-%d %H:%M:%S}] =="
}

testConstraint ChangeTimePrivilege [expr {![catch { timejump 0 minutes }]}]
testConstraint NewVWaitAfter	   [expr {![catch { vwait 0 nothing }]}]

# provide monotonic command, if "clock monotonic" not available (old core):
if {![catch {::tcl::clock::monotonic}]} {
  namespace inscope ::tcl::clock {namespace export monotonic}
  namespace import ::tcl::clock::monotonic
} else {
  proc monotonic {} {
    variable tickFactor
    # warming-up:
    clock clicks; clock microseconds
    # calc tick2usec factor:
    set tick [::tcl::clock::clicks]; set usec [::tcl::clock::microseconds]
    after 100
    set tickFactor [expr {double([::tcl::clock::clicks] - $tick) / ([::tcl::clock::microseconds] - $usec)}]
    proc monotonic {} {
      variable tickFactor
      expr {wide([::tcl::clock::clicks] / $tickFactor)} 
    }
    monotonic
  }
}
# asynchronous function doing 3 time jumps (+1 min, +1 min, -2 minutes) in 250-ms:
proc delayedtimejump {delay {async 1} {startms {}}} {
  # async code (executed in separate process):
  if {$async} {
    set code ""
    foreach pr {timejump delayedtimejump} {
	append code [list proc $pr [info args $pr] [info body $pr]] \n
    }
    append code "delayedtimejump $delay 0 [::tcl::clock::milliseconds]"
    exec [info nameofexecutable] << $code &
    return
  }
  # time-jumps (1st in 250-ms, 2nd and 3rd in given delay):
  set delay1 250
  if {$startms ne {}} {
     set delay1 [expr {250 - ([::tcl::clock::milliseconds] - $startms)}]
     if {$delay1 < 0} { set delay1 0 }
  }
  after $delay1; timejump +1 minutes
  after $delay; timejump +1 minutes
  after $delay; timejump -2 minutes
}

proc test-timer-with-jump {code maxCount expMT {maxOffs 100}} {
    set mitm [set sttm [clock seconds]]
    set stmt [monotonic]
    set expMT [expr {$expMT * 1000}]
    set maxOffs [expr {$maxOffs * 1000}]
    set res {}
    set jumped 0
    for {set i 0} {$i < $maxCount} {incr i} {
	set rt [::tcl::clock::microseconds]
	set mt [monotonic]
	# execute:
	if 1 $code
	# verify monotonic time:
	set mt [expr {[monotonic] - $mt}]
	set rt [expr {[::tcl::clock::microseconds] - $rt}]
	# give +/- 100-ms for some slow or busy systems:
	if {$mt < $expMT - $maxOffs || $mt > $expMT + $maxOffs} {
	  lappend res "$i. too large discrepancy mt: $mt ~ $expMT (rt: $rt)"
	} else {
	  #lappend res "$i. debug, # mt: $mt, rt: $rt"
	}
	# be sure we are in the future and then comming back:
	if {$jumped > 2} break; # we are already back
	if {[clock seconds] - $mitm > 30 || [clock seconds] - $mitm < -30} {
	   set mitm [clock seconds]
	   incr jumped
	}
    }
    # wait for time-jump back (from the future):
    set future [clock add $sttm +30 seconds]
    set aftto [after 10000 {set tout 1}]
    while {[clock seconds] >= $future} {
	if {[vwait 100 tout]} {
	   error "unexpected case: too long wait for time-reset."
	}
    }
    after cancel $aftto
    after 10
    # result:
    if {[llength $res]} {
      return [join $res \n]
    }
    return $jumped
}

test timer-20.1 {time jump: after relative} {ChangeTimePrivilege} {
    delayedtimejump 250; # delayed time jump in 250-ms
    test-timer-with-jump {
	after 150 {set x 1}; vwait x
    } 20 150
} 3

test timer-20.2 {time jump: vwait timeout} {ChangeTimePrivilege NewVWaitAfter} {
    delayedtimejump 250; # delayed time jump in 250-ms
    test-timer-with-jump {
	vwait -timer 150 nothing; # we want listen timer only
    } 20 150
} 3

test timer-20.3 {time jump: simple sleep} {ChangeTimePrivilege} {
    delayedtimejump 250; # delayed time jump in 250-ms
    test-timer-with-jump {
	after 150
    } 20 150
} 3


# Increase delay between time-jumps (1.5s) for possible retarded reaction of 
# the notifier by absolute timers (retarded recognition of the time-jump so
# the time can jump again (also backwards) without recognition.
# Note also we cannot test absolute timers by the backwards time-jump.

test timer-20.4 {time jump: after absolute time - "after at real-time code"} {ChangeTimePrivilege NewVWaitAfter} {
    set sttm [clock seconds]
    set i 0
    foreach tm [list \
      [clock add $sttm +1 minute -2 second] \
      [clock add $sttm +2 minute -2 second] \
    ] {
      after at $tm [list set x$i 1]
      incr i
    }
    delayedtimejump 1500; # delayed time jump in 1.5s
    test-timer-with-jump {
    	# we want listen timer only:
	if {![vwait -timer 10000 x$i]} {
	   error "too long wait for \"x$i\" (should be maximal 1 second)"
	}
    } 2 250 3000; # max 3 seconds difference (compared to 1 minute)
} 2

test timer-20.5 {time jump: sleep to absolute time - "after at real-time"} {ChangeTimePrivilege NewVWaitAfter} {
    set sttm [clock seconds]
    set offsLst [list \
      [clock add $sttm +1 minute] \
      [clock add $sttm +2 minute] \
    ]
    delayedtimejump 1500; # delayed time jump in 1.5s
    test-timer-with-jump {
	upvar offsLst offsLst
	after at [lindex $offsLst $i]
    } 2 250 3000; # max 3 seconds difference (compared to 1 minute)
} 2

# cleanup
::tcltest::cleanupTests
return

# Local Variables:
# mode: tcl