mail.send(to=to, subject=subject, message=message)
schedule(send_email, args=[to, subject, message])
# ...and this goes in models/tasks.py
def send_email(to, subject, message): mail.send(to, subject, message)
Field('stop_time','datetime',default=now+datetime.timedelta(days=1))
stop_time = now + timedelta(days=90000),
1. | db.scheduler_task.insert( |
Notice that fields "times_run", "last_run_time" and "assigned_worker_name" are not provided at schedule time but are filled automatically by the workers.
--- cut -----
mail from:GoogleGroups "web2py-developers" mailing list
make speech: web2py-d...@googlegroups.com
unsubscribe: web2py-develop...@googlegroups.com
details : http://groups.google.com/group/web2py-developers
the project: http://code.google.com/p/web2py/
official : http://www.web2py.com/
Let's get to the ideas on the table....
I don't see an improvement to have it activated by default (for the above reasons): developers wanting to use scheduler are a few steps away to have it working, but need to consider the implications.
As for tasks names to be predefined, it could save you some typing, but the actual method allow you to having a single model with all the functions you need to have the application working and choose only some of them to be available for the scheduler.
If this feature is required, someone could be so nice to give an example of the implementation ? Having the functions calls as values in a dict is an ultra-valuable way of passing those around. BTW, I didn't see different implementations around.
Jsonify args and vars by default, could be easy with new "triggers" on DAL, I like it and hope it's possible. Could break backward compatibility for who is using scheduler yet, so I think the best solution is provide a schedule() function.
Importing gluon.scheduler by default ..... well, I don't see problems on that, but having a Scheduler(db) line around could be redundant (table definition one could not be in the need for, just like record versioning). Maybe a commented line in the scaffolding model is better.
Having peoples dealing with async tasks without knowing to handle them could be a problem: taking your example in consideration, one needs to code a sending mail task that watches for errors, requeue them if some specific errors arise, remove them from queue after a few trials for some other errors if you don't want to be included super-fast in a spamlist, check for actual sending success/error if you want to inform the user that the mailbox he provided is unreachable, etc.
Too much log issue by default, easily fixable. I think the info level should be enough.
Realtime visualization is an issue. For a webcomet server we'd need to include something like tornado in the scheduler and that's hardly multi-platform, and quite a heavy requirement. Having to poll the db for task statuses every n seconds could be done, but we'll face the locking problems described before with SQLite, especially when the worker is processing tasks. Not to mention that the scheduler could be running on another machine than the one "querying" its status, if you're thinking to skip scheduler_* tables on the db....
Stop time null by default, I'm ok with that, maybe the defaults are a little too much "safer"
Task marked as EXPIRED if times_run < repeats or repeats = 0 and stop_time > now , I'm okay with that.
@@ -96,6 +96,7 @@
ACTIVE = 'ACTIVE'
INACTIVE = 'INACTIVE'
DISABLED = 'DISABLED'
+EXPIRED = 'EXPIRED'
SECONDS = 1
HEARTBEAT = 3*SECONDS
@@ -293,7 +294,7 @@
self.die()
-TASK_STATUS = (QUEUED, RUNNING, COMPLETED, FAILED, TIMEOUT, STOPPED)
+TASK_STATUS = (QUEUED, RUNNING, COMPLETED, FAILED, TIMEOUT, STOPPED, EXPIRED)
RUN_STATUS = (RUNNING, COMPLETED, FAILED, TIMEOUT, STOPPED)
WORKER_STATUS = (ACTIVE,INACTIVE,DISABLED)
@@ -361,7 +362,7 @@
Field('enabled','boolean',default=True),
Field('start_time','datetime',default=now),
Field('next_run_time','datetime',default=now),
- Field('stop_time','datetime',default=now+datetime.timedelta(days=1)),
+ Field('stop_time','datetime'),
Field('repeats','integer',default=1,comment="0=unlimted"),
Field('period','integer',default=60,comment='seconds'),
Field('timeout','integer',default=60,comment='seconds'),
@@ -454,7 +455,8 @@
run_id = run_id,
run_again = run_again,
next_run_time=next_run_time,
- times_run = times_run)
+ times_run = times_run,
+ stop_time = task.stop_time)
def report_task(self,task,task_report):
logging.debug(' recording task report in db (%s)' % task_report.status)
@@ -465,8 +467,11 @@
result = task_report.result,
output = task_report.output,
traceback = task_report.tb)
+ is_expired = task.stop_time and task.next_run_time > task.stop_time and True or False
+ status = (task.run_again and is_expired and EXPIRED
+ or task.run_again and not is_expired and QUEUED or COMPLETED)
if task_report.status == COMPLETED:
- d = dict(status = task.run_again and QUEUED or COMPLETED,
+ d = dict(status = status,
next_run_time = task.next_run_time,
times_run = task.times_run)
#I'd like to know who worked my task, reviewing some logs...
@@ -539,14 +544,17 @@
now = datetime.datetime.now()
all_workers = db(sw.id>0).select()
workers = [row.worker_name for row in all_workers]
+ #set queued tasks that expired between "runs" (i.e., you turned off)
+ #the scheduler and then it wasn't expired, but now it is
+ db(ts.status.belongs((QUEUED,ASSIGNED)))(ts.stop_time<now).update(status=EXPIRED)
+
all_available = db(ts.status.belongs((QUEUED,ASSIGNED)))\
((ts.times_run<ts.repeats)|(ts.repeats==0))\
(ts.start_time<=now)\
- (ts.stop_time>now)\
+ ((ts.stop_time>now) | (ts.stop_time == None))\
(ts.next_run_time<=now)\
(ts.enabled==True)\
- (ts.group_name.belongs(self.group_names)) #\
- #(ts.assigned_worker_name <> self.worker_name)
+ (ts.group_name.belongs(self.group_names))
limit = len(workers) * 50
#if there are a moltitude of tasks, let's assign a maximum of 50 tasks per worker.
#this can be adjusted with some added intelligence (like esteeming how many tasks will
make speech: web2py-developers@googlegroups.com
unsubscribe: web2py-developers+unsubscribe@googlegroups.com
1. |
15. | Traceback (most recent call last): |