Runner details
==============

Main loop
---------

The main loop loops until it encounters a KeyboardInterrupt or a SystemExit
exception and calls the worker once a second.

Define a worker function which exits when it is called the 3rd time:

>>> work_count = 0
>>> def worker():
...     print("Working")
...     global work_count
...     work_count += 1
...     if work_count >= 3:
...         raise SystemExit(1)


Call the main loop:

>>> import gocept.runner.runner
>>> gocept.runner.runner.MainLoop(getRootFolder(), 0.1, worker)()
Working
Working
Working
>>> work_count
3


During the loop the site is set:

>>> import zope.component.hooks
>>> zope.component.hooks.getSite() is None
True
>>> def worker():
...     print(zope.component.hooks.getSite())
...     raise SystemExit(1)
>>> gocept.runner.runner.MainLoop(getRootFolder(), 0.1, worker)()
<zope.site.folder.Folder object at 0x...>



After the loop, no site is set again:

>>> zope.component.hooks.getSite() is None
True


When the worker passes without error a transaction is committed:

>>> work_count = 0
>>> def worker():
...     print("Working")
...     global work_count
...     work_count += 1
...     if work_count >= 2:
...         raise SystemExit(1)
...     site = zope.component.hooks.getSite()
...     site.worker_done = 1
>>> gocept.runner.runner.MainLoop(getRootFolder(), 0.1, worker)()
Working
Working

We have set the attribute ``worker_done`` now:

>>> getRootFolder().worker_done
1


When the worker produces an error, the transaction is aborted:

>>> work_count = 0
>>> def worker():
...     global work_count
...     work_count += 1
...     print("Working")
...     site = zope.component.hooks.getSite()
...     site.worker_done += 1
...     if work_count < 3:
...         raise ValueError('hurz')
...     raise SystemExit(1)
>>> gocept.runner.runner.MainLoop(getRootFolder(), 0.1, worker)()
Working
Working
Working


We still have the attribute ``worker_done`` set to 1:b

>>> getRootFolder().worker_done
1


Controlling sleep time
----------------------

The worker function can control the sleep time.

Register a log handler so we can observe this:

>>> from io import StringIO
>>> import logging
>>> log = StringIO()
>>> log_handler = logging.StreamHandler(log)
>>> logging.root.addHandler(log_handler)
>>> old_log_level = logging.root.level
>>> logging.root.setLevel(logging.DEBUG)


>>> work_count = 0
>>> def worker():
...     global work_count
...     work_count += 1
...     new_sleep = work_count * 0.1
...     if work_count == 3:
...         print("Will sleep default")
...         return None
...     if work_count > 3:
...         raise SystemExit(1)
...     print("Will sleep %s" % new_sleep)
...     return new_sleep
>>> gocept.runner.runner.MainLoop(getRootFolder(), 0.15, worker)()
Will sleep 0.1
Will sleep 0.2
Will sleep default

The real sleep values are in the log:

>>> print(log.getvalue())
new transaction
commit
Sleeping 0.1 seconds
new transaction
commit
Sleeping 0.2 seconds
new transaction
commit
Sleeping 0.15 seconds
new transaction
abort...

When an error occours within the worker, the default sleep time will be used:

>>> _ = log.seek(0)
>>> _ = log.truncate()
>>> work_count = 0
>>> def worker():
...     global work_count
...     work_count += 1
...     if work_count == 1:
...         new_sleep = 0.1
...     elif work_count == 2:
...         print("Failing")
...         raise Exception("Fail!")
...     elif work_count == 3:
...         print("Will sleep default")
...         return None
...     elif work_count > 3:
...         return gocept.runner.Exit
...     print("Will sleep %s" % new_sleep)
...     return new_sleep
>>> gocept.runner.runner.MainLoop(getRootFolder(), 0.15, worker)()
Will sleep 0.1
Failing
Will sleep default

The real sleep values are in the log:

>>> print(log.getvalue())
new transaction
commit
Sleeping 0.1 seconds
new transaction
Error in worker: Exception('Fail!'...)
Traceback (most recent call last):
  ...
Exception: Fail!
abort
Sleeping 0.15 seconds
new transaction
commit
Sleeping 0.15 seconds
new transaction
commit...

Restore old log handler:

>>> logging.root.removeHandler(log_handler)
>>> log.close()
>>> logging.root.setLevel(old_log_level)
