Ladon Tasks =========== Description ----------- Ladon tasks are ladonzed methods that are executed in the background - also called task-type methods. The Ladon framework takes care of almost everything so you as a service developer can concentrate on the code. There is essentially no difference in making a "normal" ladonized method and a task-type ladonized method. The only thing you as a developer have to think of is protecting access to non-local objects. So if you are keeping state between several method requests in a global object you will need to use some sort of locking mechanism. Example ------- Let's say for instance you are implementing a web service interface for a virtualization center, then one of your methods might look like this:: class InstanceService(object): @ladonize(PORTABLE_STRING, PORTABLE_STRING, rtype=InstanceInformation) def cloneInstance(self,instance_id,new_instance_name, **kw): response = InstanceInformation() ... ... return response In this example we imagine that the method **cloneInstance** will create a new instance with the name **new_instance_name** as an exact copy of the existing instance identified by **instance_id**. The result of the clone operation is returned in the *LadonType* **InstanceInformation**. It is not hard to imagine that such an operation will take time having tasks like: - Configuring and creating a new virtual destination instance - Copying all disk images from the source instance - Maybe starting it up as an option So let's say that this operation takes minutes. If you expose the method like above the requesting connection will block for just as long time waiting for the response to come back. Also the back-end has no way of reporting back to the end-user how far into the operation it has progressed. To effectively solve all of the above problems expose the method as a Ladon task instead:: class InstanceService(object): @ladonize(PORTABLE_STRING, PORTABLE_STRING, rtype=InstanceInformation, tasktype=True) def cloneInstance(self,instance_id,new_instance_name, **kw): response = InstanceInformation() ... kw['update_progress'](0.3) ... return response The only extra code you will need to add in the task-type implementation is progress updating. What is the effect for the end-user ----------------------------------- So how does task-types really affect your service? Well in pratice what we have done is created a server-side asynchronious method which starts the method code, but instead of returning the a **InstanceInformation** *LadonType* response as was defined it returns a **task_id** immediately in a LadonType called **TaskInfoResponse**:: class TaskInfoResponse(LadonType): task_id = { 'type': int, 'doc': u'unique integer identifying the task to that has been created' } So this will start the task. To continuously monitor the progress from the user-end just call the task's corresponding _progress() method which returns a LadonType called **TaskProgressResponse**:: class TaskProgressResponse(LadonType): task_id = { 'type': int, 'doc': u'unique integer identifying the task to that has been probed' } progress = { 'type': float, 'doc': u'Floating point value between 0 and 1 denoting the progress in percent' } duration = { 'type': float, 'doc': u'Floating point value that informs how long time in seconds the task as run' } starttime = { 'type': float, 'doc': u'The start time in seconds since the epoch as a floating point number' } When the task has finished running the result can be retreived using the task's _result() method. It returns the type you originally defined your task-method to return using the ladonize decorator's rtype keyword. To summerize let's see what the effect of the above example would be for the end-user. As already explained the method you originally defined and exposed has been split into 3 exposed methods. So the method from the example **cloneInstance** will expand to the following methods at the user-end: - cloneInstance(self, instance_id, new_instance_name) - returning a **TaskInfoResponse** - cloneInstance_progress(self, task_id) - returning a **TaskProgressResponse** - cloneInstance_result(self, taskid) - returning the return type defined in the ladonize decorator: **InstanceInformation** Updating a task's progress -------------------------- Progress values in Ladon are defined as float values between 0 and 1 denoting the progress in percent. Once a task has been started it must update it's own progress continuously. Needless to say the more frequently you as a service developer update the progress the better the resolution and experience the end-user will get. Ladon will initially set a new task's progress to 0.0 and also set it to 1.0 once the task has ended. So even if you somehow can't update the progress the task will work as it should. The function for updating a task's progress is passed to the method via the keyword argument **update_progress**. It takes a float between 0 and 1 as argument. Values less than 0 and larger than 1 will be converted to 0 and 1 respectively. Here is a full Ladon task example using progress update:: from ladon.compat import PORTABLE_STRING from ladon.ladonizer import ladonize import time class TaskService(object): @ladonize(PORTABLE_STRING, int, rtype=int, tasktype=True) def taskExample(self,session_id,counter,**kw): set_progress = kw['update_progress'] repeat = 60.0 for i in range(int(repeat)): time.sleep(.5) set_progress(float(i+1.0)/repeat) return counter Threading vs. multiprocessing ----------------------------- From version 1.0.0+ support for multiprocessing based services has been introduced. Versions lower than 1.0.0 only support threaded task services. This has some rather serious cave-eats: - Threading module used, meaning that **GIL** is in play - Task state is kept in global python variables and therefore do not work cross-host nor cross-process Threaded tasks `````````````` In many use-cases the cave-eats mentioned above are not a problem. Ie. if your task spawns a new synchronious process like a system-call you will not be having problems with *GIL* and the current implementation will work as long as your application stays within the same process. The good thing about the current solution is that it works out-of-the-box without introducing thirdparty state-keeping services. It should also be noted that if you are using ie. mod-wsgi on apache2 it is possible to control process groups for specific Ladon services, so you can configure your apache server to use only one process for your services containing task type Ladon methods, and multiple procesesses for your other services that do not need to keep state like Ladon tasks:: ServerName my-site.com ServerAdmin webmaster@localhost WSGIDaemonProcess my-site.com user=jakob group=www-data threads=25 WSGIProcessGroup my-site.com WSGIScriptAlias / /srv/my-site-service/handler.py Require all granted In the above mod-wsgi example, all requests are kept in the same process with up to 25 threads. Multiprocessed tasks ```````````````````` By installing one of the supported caching systems (memcached or redis) you have the option to configure socket-based state caching. When using the thirdparty state caching option Ladon will shift over to using the multiprocessing module for task execution. This also efficiently side-steps the GIL limitation. You can install the cache system on any server and then run your service on as many hosts as you need and load balance traffic between them. You just have to configure all the hosts to target the same cache server for state-keeping. You do this through the same ladonize option **tasktype** but instead of just passing **True** you pass a dictionary that contains information about the state-keeping cache system. Example:: from ladon.compat import PORTABLE_STRING from ladon.ladonizer import ladonize import time class TaskService(object): @ladonize(PORTABLE_STRING, int, rtype=int, tasktype={"enable": True, "redis_server": "10.1.25.200"}) def taskExample(self,session_id,counter,**kw): set_progress = kw['update_progress'] repeat = 60.0 for i in range(int(repeat)): time.sleep(.5) set_progress(float(i+1.0)/repeat) return counter Tasktype cache system options are: **redis_server** and **memcache_server** .. toctree:: :maxdepth: 2