20130221

Unit testing produces problems

The release with yesterday’s small bugfixes was almost ready:

But there was time left before the evening, so I started to clean up the test suite. This included a series of little internal optimizations in the startup process (details see yesterday (German only). Everything seemed fine and innocent until I deployed it on the lino-framework.org server: some strange failures, probably due to threading issues. Finally these small optimizations I made for the test suite had triggered an avalanche of changes, each of them very welcome and probably making Lino better in the long run… but not really at the right moment. Of course these surprises disturb my release process only because I don’t use branching as I should…

Anyway, these problems are solved in http://code.google.com/p/lino/source/detail?r=18584dc2ff18.

And still I’ll spend the rest of today on the unit tests (because the long-term advantages are more important than the short-term risk) and without a branch (because branching does cost some “administrative” overhead, and because I’d rather enjoy my freedom as long as I am the only Lino developer).

Insgesamt wird der Startup-Vorgang durch die gestrigen Änderungen geradliniger und klarer. Einer der kleinen Nebeneffekte ist, dass die SiteConfig-Instanz jetzt schon gleich beim Startup eingelesen und gecacht wird. Was zu einem coolen Bug führte: in initdb und auch initdb führte das zu einem Problem, das nur jedes zweite Mal auftrat: wenn die Datenbank von einem vorigen Lauf noch initialisiert war, enthielt die SiteConfig Verweise auf andere Objekte wie den Sektor, job_agent usw. Aber die wurden ja dann gelöscht. Und wenn dumppy die gecachte SiteConfig dann speicheren wollte, bekam er bein full_clean einen ValidationError.

Lino startup in multi-threaded environment

I spent another hour to document a problem which took me a few hours to understand.

"""
Trying to explain the problem that occured today.

Thanks to http://www.dabeaz.com/python/UnderstandingGIL.pdf

"""

from time import sleep
from threading import Thread

class Lino(object):
    
    done = False
    doing = False
        
    def startup(self):
        if self.done:  return
        if self.doing:
            #~ raise Exception("doing")
            return
        self.doing = True
        
        # simulate a big work to do before the `ui` attribute can be added
        print "Starting up",
        for i in range(3): print '.', ; sleep(0.5)
        print '. done'
        self.ui = "yes"
          
        self.done = True
        self.doing = False
        
# A Django process has one global Lino instance in `settings.LINO`.
LINO = Lino() 

# Parameters to play with:
SINGLE_THREADED = False # multi-threaded, e.g. mod_wsgi
#~ SINGLE_THREADED = True # single threaded, e.g. development server
#~ WAIT_BEFORE_ACCESSING_UI = 0
WAIT_BEFORE_ACCESSING_UI = 2


def main(name):
    print "Incoming %s:" % name
    LINO.startup()
    if WAIT_BEFORE_ACCESSING_UI:
        sleep(WAIT_BEFORE_ACCESSING_UI)
    print LINO.ui
    

if __name__ == '__main__':
    print "SINGLE_THREADED is %s, WAIT_BEFORE_ACCESSING_UI is %s" % (SINGLE_THREADED, WAIT_BEFORE_ACCESSING_UI)
    if SINGLE_THREADED: 
        main('request1')
        main('request2')
    else:
        # We simulate what mod_wsgi does:
        # Each incoming http request starts a new thread.
        t1 = Thread(target=main,args=('request1',))
        t2 = Thread(target=main,args=('request2',))
        t1.start(); t2.start()
        t1.join(); t2.join()
    

Constellation 1 is on a development server:

SINGLE_THREADED is True, WAIT_BEFORE_ACCESSING_UI is 0
Incoming request1:
Starting up . . . . done
yes
Incoming request2:

Constellation 2 on a mod_wsgi until yesterday:

SINGLE_THREADED is False, WAIT_BEFORE_ACCESSING_UI is 2
Incoming request1:
Starting up . Incoming request2:
. . . done
yes
yes

Constellation 3 on a mod_wsgi after yesterday:

SINGLE_THREADED is False, WAIT_BEFORE_ACCESSING_UI is 0
Incoming request1:
Starting up . Incoming request2:
Exception in thread Thread-2:
Traceback (most recent call last):
  File "c:\Python27\lib\threading.py", line 530, in __bootstrap_inner
    self.run()
  File "c:\Python27\lib\threading.py", line 483, in run
    self.__target(*self.__args, **self.__kwargs)
  File "0221.py", line 49, in main
    print LINO.ui
AttributeError: 'Lino' object has no attribute 'ui'

. . . done
yes