Thursday 26 February 2009

Implementing code reloading, part 2

This post continues on from Implementing code reloading, part 1.

The biggest limitation when it comes to code reloading is running code.

  • Running code has local variables and external changes to these cannot be made.
  • Running code has bytecode which is in mid-execution and which cannot be replaced until that execution has finished.
Some illustration of these problems will be given below.

Local variables

This will create a function which when run as a Stackless tasklet, blocks in mid-execution on a Stackless channel.
>>> import stackless
>>> c = stackless.channel()
>>> def f():
... a = 1
... b = {}
... c.receive()
Next the tasklet is created and run, leaving it blocked on the channel.
>>> t = stackless.tasklet(f)()
>>> t.run()
The function in mid-execution has a frame encapsulating its running state, like the local variables within that executing function. These variables can be accessed as a dictionary. Sort of.
>>> dir(t)
[..., 'frame', ...]
>>> dir(t.frame)
[..., 'f_locals', ...]
>>> t.frame.f_locals
{'a': 1, 'b': {}}
>>> t.frame.f_locals['a'] = 2
>>> t.frame.f_locals
{'a': 1, 'b': {}}
What this demonstrates is that there is no real local variable dictionary. The one obtained above is actually constructed on request.

The repercussions of this are that code in mid-execution cannot be satisfactorily updated to reflect code reloading changes. That code has to be able to continue executing without error using whatever objects it is holding onto, whatever the approach taken to code reloading is.

Methods

A method is a reference to a function, whether it is obtained from a class or an instance. In the following case, a method is being obtained from a class.
>>> class X:
... def f(self):
... pass
...
>>> X.f
<unbound method X.f>
Methods have attributes which refer to the class (im_class) and function involved (im_func), and possibly also an instance (im_self). Bound methods are taken from instances and have values for all three attributes, whereas unbound methods are taken from classes and do not have an instance. All of these attributes are read only and cannot be changed.
>>> dir(f)
[..., 'im_class', 'im_func', 'im_self']
>>> f.im_class
<class __main__.X at 0x01A1F720>
>>> f.im_class = X
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: readonly attribute
So methods stored as local variables of frames are effectively unchangeable references to the classes and functions they were taken from, both of which can be updated by code reloading. The existing references held in the methods stay in use until the code the frame is associated with, exits.

If the approach taken to code reloading relies on old versions of updated classes not being used after the update, then this is problematic. Whatever approach taken to code reloading, the stale function reference may continue to be used, and programmers need to be aware of that as well.

No comments:

Post a Comment