- Notifications
You must be signed in to change notification settings - Fork62
Pickling
One of the main features ofStackless is its ability topickle and unpickletasklets. That means that a runningprogram inside a tasklet can be persistently stored to afile or string. Later, it can be restored again and cancontinue to run at the point where it was previouslyhalted. This need not be on the same machine!
Example -Pickling and unpickling simple tasklets:
fromstacklessimportrun,schedule,taskletimportpickledefaCallable(name):print" aCallable<%s>: Before schedule()"%nameschedule()print" aCallable<%s>: After schedule()"%nametasks= []fornamein"ABCDE":tasks.append(tasklet(aCallable)(name))print"Schedule 1:"schedule()printprint"Pickling..."pickledTasks=pickle.dumps(tasks)printprint"Schedule 2:"schedule()unpickledTasks=pickle.loads(pickledTasks)fortaskinunpickledTasks:task.insert()printprint"Schedule Unpickled Tasks:"schedule()
Subclassing thetasklet orchannel classes ispretty straightforward. In fact, if you need to persistany extra information with your tasklets or channels,you will need to do this. Neither of the classes havetheirslots or instance dictionary persisted with themwhen pickled.
The following example should illustrate exactly whatthe problem is.
Example -Named tasklets:
classCustomTasklet(stackless.tasklet):name="UNNAMED"def__str__(self):return"<CustomTasklet(name='%s')>"%self.namedeff():passt1=CustomTasklet(f)()t1.name="Pointless"s=pickle.dumps(t1)t2=pickle.loads(s)
Output:
>>>print t2.name== t1.nameFalse>>>print t2.name'UNNAMED'
However, you can easily get around this by overriding__reduce__() /__reduce_ex__() and__setstate__()to add whatever else you want to be persisted.
Example -Named tasklets that persist their name:
classCustomTasklet(stackless.tasklet):name="UNNAMED"def__str__(self):return"<CustomTasklet(name='%s')>"%self.name# When this is present, it is called in lieu of __reduce__.# As the base tasklet class provides it, we need to as well.def__reduce_ex__(self,pickleVersion):returnself.__reduce__()def__reduce__(self):# Get into the list that will eventually be returned to# __setstate__ and append our own entry into it (the# dictionary of instance variables).ret=list(stackless.tasklet.__reduce__(self))l=list(ret[2])l.append(self.__dict__)ret[2]=tuple(l)returntuple(ret)def__setstate__(self,l):# Update the instance dictionary with the value we added in.self.__dict__.update(l[-1])# Let the tasklet get on with being reconstituted by giving# it the original list (removing our addition).returnstackless.tasklet.__setstate__(self,l[:-1])deff():passt1=CustomTasklet(f)()t1.name="Pointless"s=pickle.dumps(t1)t2=pickle.loads(s)
Output:
>>>print t2.name== t1.nameTrue>>>print t2.name'Pointless'
Pickling a tasklet that is blocked on a channel, willnot result in the pickling of that channel unless youare explicitly pickling a reference to that channelalong with it.
If a tasklet is blocked on a channel that is not inany scope contained in the function the tasklet isbound to, then it will not be pickled.
Example -Channel not pickled:
deff():c.receive()c=stackless.channel()t1=stackless.tasklet(f)()s=pickle.dumps(t1)t2=pickle.loads(s)# The tasklet will not be attached to a channel.assertt2._channelisNone
Example -Channel pickled:
deff(c):c.receive()c=stackless.channel()t1=stackless.tasklet(f)(c)s=pickle.dumps(t1)t2=pickle.loads(s)# The tasklet will be attached to a channel.assertt2._channelisnotNone
Pickling a channel, will also pickle any taskletscurrently blocked on it. But sometimes you just want topickle the channel with only some of those tasklets stillblocked on it, or perhaps, tasklets you isolate from thechannel on their own. You can do this by using__reduce__() and__setstate__().
Example -Removing a tasklet from a channel andpickling the results:
# Given a channel 'c' with four tasklets blocked on it, where# we want to just pickle the first.# Get the channel state.x,y, (balance,flags,tasklets)=c.__reduce__()# Get the tasklet and remove it from the ones on the channel.t=tasklets[0]deltasklets[0]# Rebuild the channel without the tasklet. You do not need to# bother adjusting the balance for the changes you made to the# list of blocked tasklets, as it is recalculated automatically.# This will replace the channels existing state. But if you# want to keep the channel as it is, you can create a new# and use it in place of 'c'.c.__setstate__((balance,flags,tasklets))# Pickle just the tasklet (and whatever it holds a reference to).s1=pickle.dumps(t)# Pickle the channel which no longer has that channel.s2=pickle.dumps(c)
Two important things to know about the values you getfrom__reduce__() and pass to__setstate__():
- The list of tasklets, featured in both functions, is in theorder in which the tasklets came to be blocked on the channel.
- When passed to
__setstate__()the actual balance value isnot used, except for its sign, which is used to indicate whetherthe blocked tasklets are receiving or sending.