Please read the document ``Working with processes'' if you do not yet know about processes, priorities, suspension etc.
Time
, which represent time-of-day values,
of AbsoluteTime
, which represent a particular time
at a specific date and finally millisecondClock values,
which are the values of a free running clock, incremented every millisecond.
Currently, all delaying is internally based upon the millisecondClock value.
This value has only a limited precision (i.e. number of bits) and therefore
overruns in regular intervals. Although the details depend on the particular
OperatingSystem internals, this interval guaranteed to be greater than 24 hours.
On Unix, a flip through 0 occurs about every 74 hours.
Since Smalltalk/X's smallIntegers are represented in 31 bits,
it is (currently) not possible to represent time intervals longer
than HALF of the range (i.e. about 37 hours) using the millisecondClock value.
(see OperatingSystem » maximumMillisecondTimeDelta
)
Therefore, it is (currently) not possible to directly use delays on times longer than this interval. To do this, you have to write a little program doing this with a workaround (i.e. delaying in smaller steps until the desired time and/or time-of-day has passed).
The operatingSystems millisecondClock offers a limited precision. Typical internal resolutions are 1/100, 1/60, 1/50 or even 1/20 of a second. Thus, when asking for the millisecondClocks value after a short time interval may return you the same value as before, even when some milliseconds actually have passed in between.
Since millisecondClock values wrap, you cannot perform arithmetic directly
upon them. Class OperatingSystem
provides methods which know about the wrap
and handle it correctly.
See:
Note:
OperatingSystem » millisecondTimeDeltaBetween:and:
OperatingSystem » millisecondTimeAdd:and:
OperatingSystem » millisecondTime:isAfter:
We are not perfectly happy with this solution - there ought to be some
"WrappingNumber" subclass of Number, which does all this transparently.
d := Delay forSeconds:numberOfSeconds
or:
d := Delay forMilliseconds:numberOfMillis
The actual delaying is done by sending wait
to this delay.
|d|
d := Delay forSeconds:3.
d wait
you will notice, that only the current process is suspended for the
delay time. Other processes continue to execute (open some animated
demo - for example, the CubeDemo - to see this).
For short delays, use forMilliseconds
, as in:
There are also `combined create & wait' interfaces available in the
(Delay forMilliseconds:100) wait
Delay
class:
or:
Delay waitForMilliseconds:100
Delay waitForSeconds:10
This means, that if you call for a delay in a loop, the individual errors
will accumulate. It is therefore not possible, to maintain a heart-beat
kind of time handling by simply performing delays in a loop.
The example:
will NOT finish after 5 seconds, but some time later. On an unloaded system,
the error is typically a few percent, but, depending on other
processes running on your machine, the error may become substantial.
|d t1 t2 delta|
t1 := Time millisecondClockValue.
d := Delay forMilliseconds:50.
100 timesRepeat:[
d wait.
"do something"
].
t2 := Time millisecondClockValue.
delta := OperatingSystem millisecondTimeDeltaBetween:t2 and:t1.
Transcript show:'delta time is '; show:(delta); showCR:' milliseconds'.
Also, the actual delay time also depends on the resolution of the
machines clock: for example, if the minimum resolution is 60ms, all delays
smaller than that time interval will always delay for at least that time.
(therefore, the above example may take 6 or even 10 seconds to complete,
with clock resolutions of 60ms or 100ms !)
To fix this problem, Delay
allows waiting for a specific time to be reached,
(in addition to the deltatime based wait described above).
Try:
of course, there is also a nonzero error in each individual wait,
but this error will not accumulate.
|t1 t2 now then delta|
t1 := Time millisecondClockValue.
now := Time millisecondClockValue.
100 timesRepeat:[
then := OperatingSystem millisecondTimeAdd:now and:50.
(Delay untilMilliseconds:then) wait.
now := then
].
t2 := Time millisecondClockValue.
delta := OperatingSystem millisecondTimeDeltaBetween:t2 and:t1.
Transcript show:'delta time is '; show:(delta); showCR:' milliseconds'.
Therefore, the relative error will approach zero over time.
Use the above algorithm, if you need some action to be performed in
constant intervals.
#resume
to the delay.
|process d|
process :=
[
Transcript show:(Time now);
showCR:' subprocess: going to wait for half an hour ...'.
d := Delay forSeconds:1800.
d wait.
Transcript show:(Time now);
showCR:' subprocess: here I am again ...'.
Transcript show:(Time now);
showCR:' subprocess: done.'
] fork.
"after some short time, stop the wait"
Transcript show:(Time now); showCR:' main process: wait a bit'.
Delay waitForSeconds:2.
Transcript show:(Time now); showCR:' main process: wakeup the delay early now'.
d resume.
Transcript show:(Time now); showCR:' main process: done.'.
Process priorities also have an influence; in the following example,
the subprocess will start right-away and resume immediately (before the
parent process outputs the 'done' message):
|process d|
process :=
[
Transcript show:(Time now);
showCR:' subprocess: going to wait for half an hour ...'.
d := Delay forSeconds:1800.
d wait.
Transcript show:(Time now);
showCR:' subprocess: here I am again ...'.
Transcript show:(Time now);
showCR:' subprocess: done.'
] forkAt:(Processor activePriority + 1).
"after some short time, stop the wait"
Transcript show:(Time now); showCR:' main process: wait a bit'.
Delay waitForSeconds:2.
Transcript show:(Time now); showCR:' main process: wakeup the delay early now'.
d resume.
Transcript show:(Time now); showCR:' main process: done.'.
ProcessorScheduler
class.
Through the global
variable Processor
(which is the one-and-only instance of ProcessorScheduler
)
you can tell the processor to signal a semaphore whenever some time has been
reached, or input arrives on an external stream.
The following example waits until either some input arrives on
a pipe-stream, or 5 seconds have expired,
Actually, there is already a method which does exactly this:
we show the code here anyway, for didactic reasons:
ExternalStream readWaitWithTimeout:
in the previous example, the semaphore was signalled by data arriving
in the pipe; in the following, a timeOut will trigger the semaphore:
|sema pipe|
"/ create a pipeStream - there will be data after 1 second:
pipe := PipeStream readingFrom:'(sleep 1; echo hello)'.
Transcript show:Time now; showCR:' pipe created'; endEntry.
"/ create a semaphore, arrange for it to be signalled when
"/ either data arrives, or 5 seconds have passed
sema := Semaphore new.
Processor signal:sema onInput:(pipe fileDescriptor).
Processor signal:sema afterMilliseconds:5000.
"/ now wait
sema wait.
Transcript show:Time now; showCR:' after wait'; endEntry.
"/ data or timeout ?
pipe canReadWithoutBlocking ifTrue:[
Transcript show:Time now; showCR:' data available'; endEntry
] ifFalse:[
Transcript show:Time now; showCR:' no data available'; endEntry
].
"/ cleanup
Transcript show:Time now; showCR:' closing pipe'; endEntry.
pipe shutDown.
Transcript show:Time now; showCR:' done'; endEntry.
Notice:
|sema pipe|
"/ create a pipeStream - there will be data after 15 second:
pipe := PipeStream readingFrom:'(sleep 15; echo hello)'.
Transcript show:Time now; showCR:' pipe created'; endEntry.
"/ create a semaphore, arrange for it to be signalled when
"/ either data arrives, or 5 seconds have passed
sema := Semaphore new.
Processor signal:sema onInput:(pipe fileDescriptor).
Processor signal:sema afterMilliseconds:5000.
"/ now wait
sema wait.
Transcript show:Time now; showCR:' after wait'; endEntry.
"/ data or timeout ?
pipe canReadWithoutBlocking ifTrue:[
Transcript show:Time now; showCR:' data available'; endEntry
] ifFalse:[
Transcript show:Time now; showCR:' no data available'; endEntry
].
"/ cleanup
Transcript show:Time now; showCR:' closing pipe'; endEntry.
pipe shutDown.
Transcript show:Time now; showCR:' done'; endEntry.
The close method in pipeStream waits for the underlying unix
command to finish correctly (this is done in the underlying pclose()
system function and not a smalltalk feature).
Therefore, we better use the alternative shutDown
method
(which does not wait) to close the pipeStream in the second example.
You can use shutDown
just like close
with ordinary
streams - they behave the same, except for pipeStreams and socket connections.
using the existing waitWithTimeout-mechanism from ExternalStream
the above is of course the same as:
Try it with data arriving earlier in a workspace.
|pipe|
"/ create the pipe - data will arrive after 15 seconds
pipe := PipeStream readingFrom:'(sleep 15; echo hello)'.
Transcript show:Time now; showCR:' pipe created'; endEntry.
"/ wait, but no longer than 5 seconds
(pipe readWaitWithTimeout:5) ifTrue:[
Transcript show:Time now; showCR:' data available'; endEntry
] ifFalse:[
Transcript show:Time now; showCR:' no data available'; endEntry
].
"/ cleanup
Transcript show:Time now; showCR:' closing pipe'; endEntry.
pipe shutDown.
Transcript show:Time now; showCR:' done'; endEntry.
Processor addTimedBlock:aBlock afterSeconds:seconds
or:
Processor addTimedBlock:aBlock afterMilliseconds:millis
or:
Processor addTimedBlock:aBlock atMilliseconds:aMillisecondsClockValue
The currently running process will be interrupted in whatever it is doing
when the time has come; if suspended, it will be resumed.
"aBlock value"
,
you can perform
all kind of actions in the block: raise a signal, do a block-return,
terminate the process etc.
You can also force an immediate interrupt and have another process evaluate
a block:
to arrange for this to occur after some time, use:
aProcess interruptWith:aBlock
example:
Processor addTimedBlock:[
aProcess interruptWith:aBlock
] afterSeconds:timeTillInterrupt
example:
Processor
addTimedBlock:[Transcript showCR:'interrupt occured'; endEntry]
afterSeconds:1.
Transcript showCR:'start processing ...'; endEntry.
1 to:10 do:[:i |
Transcript showCR:i; endEntry.
1000 factorial
].
Transcript showCR:'done.'; endEntry
Notice:
The system uses this interrupt mechanism to interrupt a process when "Ctrl-C"
is pressed.
|p|
p := [
Transcript showCR:'subprocess start.'; endEntry.
1 to:20 do:[:i |
Transcript showCR:i; endEntry.
1000 factorial.
].
Transcript showCR:'subprocess end.'; endEntry.
] forkAt:4.
Transcript showCR:'waiting for a while ...'; endEntry.
(Delay forSeconds:3) wait.
Transcript showCR:'now killing subprocess ...'; endEntry.
p interruptWith:[p terminate].
Transcript showCR:'done.'; endEntry
On the lowest level, the runtime system reacts to interrupts by
sending messages to so called interrupt handler objects
at the time the interrupt occurs.
These objects are responsible for signalling semaphores, rescheduling processes
and
to implement the above described highlevel interrupt behavior.
At system startup time, Smalltalk/X installs appropriate handler
objects as interrupt handler.
(see
"Smalltalk initInterrupts
"
,
or
"ProcessorScheduler initialize
"
for some examples which install low level handlers.)
You can (*) install your own interrupt handler objects; situations in which this makes sense are:
ObjectMemory
class.
These class variables are known to the runtime system - you may not remove
them
(actually, you could do this - the VM would then no longer send any
interrupt messages ... but why would you want to do this ?).
The handler variables and messages sent to them are:
InternalErrorHandler internalError:
aString
UserInterruptHandler userInterrupt
SIGINTR
arrives).
CTRL-C
-interrupt
in a smalltalk window.
TimerInterruptHandler timerInterrupt
SIGALRM
) handler.
SpyInterruptHandler spyInterrupt
SIGVTALARM
or SIGALRM
).
StepInterruptHandler stepInterrupt
ExceptionInterruptHandler exceptionInterrupt
SIGFPE
).
NaN
(Not-a-Number).
ErrorInterruptHandler errorInterrupt
MemoryInterruptHandler memoryInterrupt
ChildSignalInterruptHandler childInterrupt
IOInterruptHandler ioInterrupt
SIGIO
) notification.
SignalInterruptHandler signalInterrupt:
signalNumber
DisposeInterruptHandler disposeInterrupt
RecursionInterruptHandler recursionInterrupt
CustomInterruptHandler customInterrupt
Object
class.
Therefore, every object understands and responds to those messages.
For each handler, ObjectMemory
provides a message to get
and set the corresponding handler objects. Like signal access methods,
these are named after the corresponding handlers variable name.
For example,
returns, and
ObjectMemory userInterruptHandler
sets the
ObjectMemory userInterruptHandler:someObject
UserInterruptHandler
.
(*) WARNING:
You may easily make the system inoperatable when playing around with these handlers. So be prepared for malfunctions or deadlocks when changing things in this area (think twice and save your work before doing so).
disposeInterrupt
is used by the
memory/resource management and controls object finalization.
timerInterrupt
and ioInterrupt
are used by the
scheduler.
userInterrupt
for console interrupt processing.
Copyright © 1995 Claus Gittinger, all rights reserved
<cg at exept.de>