root/branches/release-41/lib/MT/TaskMgr.pm @ 2746

Revision 2746, 8.9 kB (checked in by bchoate, 17 months ago)

Updated POD for MT::Page, MT::TaskMgr.

  • Property svn:keywords set to Id Revision
Line 
1# Movable Type (r) Open Source (C) 2001-2008 Six Apart, Ltd.
2# This program is distributed under the terms of the
3# GNU General Public License, version 2.
4#
5# $Id$
6
7package MT::TaskMgr;
8
9use strict;
10use base qw( MT::ErrorHandler );
11
12use MT::Task;
13use Fcntl qw( :DEFAULT :flock );
14use Symbol;
15our (%Tasks, $inst);
16
17sub instance {
18    $inst ||= new MT::TaskMgr;
19}
20
21sub new {
22    my $mgr = bless {}, shift;
23    $mgr->init();
24    return $mgr;
25}
26
27sub init {
28    my $mgr = shift;
29    return if $mgr->{initialized};
30    %Tasks = %{ MT->registry("tasks") || {} };
31    MT->run_callbacks('tasks', \%Tasks);
32    $mgr->{initialized} = 1;
33}
34
35sub run_tasks {
36    my $mgr = shift;
37    my (@tasks_to_run) = @_;
38
39    if (!ref($mgr)) {
40        $mgr = $mgr->instance;
41    }
42
43    @tasks_to_run = keys %Tasks unless @tasks_to_run;
44
45    if ($mgr->{running}) {
46        warn "Attempt to recursively invoke TaskMgr.";
47        return;
48    }
49
50    local $mgr->{running} = 1;
51
52    # Secure lock before running tasks
53    my $unlock;
54    unless ($unlock = $mgr->_lock()) {
55        MT->log({
56            class => 'system',
57            category => 'tasks',
58            level => MT::Log::ERROR(),
59            message => MT->translate("Unable to secure lock for executing system tasks. Make sure your TempDir location ([_1]) is writable.", MT->config->TempDir)
60        });
61        return;
62    }
63
64    eval {
65        my $app = MT->instance;
66
67        $app->run_callbacks('PeriodicTask');
68
69        require MT::Log;
70        require MT::Session;
71        my @completed;
72
73        foreach my $task_name (@tasks_to_run) {
74            my $task = $Tasks{$task_name} or next;
75
76            if (ref $task eq 'HASH') {
77                $task->{key} ||= $task_name;
78                $task = $Tasks{$task_name} = MT::Task->new($task);
79            }
80
81            my $name = $task->label();
82            my $sess = MT::Session->load({
83                id => 'Task:' . $task->key,
84                kind => 'PT'
85            });
86            next if $sess && ($sess->start + $task->frequency > time);
87            if (!$sess) {
88                $sess = MT::Session->new;
89                $sess->id('Task:' . $task->key);
90                $sess->kind('PT');
91            }
92
93            # Run this task
94            my $status;
95            eval {
96                local $app->{session} = $sess;
97                $status = $task->run;
98            };
99            if ($@) {
100                my $err = $@;
101                $app->log({
102                    class => 'system',
103                    category => 'tasks',
104                    level => MT::Log::ERROR(),
105                    message => $app->translate("Error during task '[_1]': [_2]", $name, $err),
106                    metadata => MT::Util::log_time() . ' '
107                        . $app->translate("Error during task '[_1]': [_2]", $name, $err)
108                });
109            } else {
110                push @completed, $name if (defined $status) && ($status ne '') && ($status > 0);
111 
112            }
113
114            $sess->start(time);
115            $sess->save;
116        }
117        if (@completed) {
118            $app->log({
119                class => 'system',
120                category => 'tasks',
121                level => MT::Log::INFO(),
122                message => $app->translate("Scheduled Tasks Update"),
123                metadata => MT::Util::log_time() . ' ' . $app->translate("The following tasks were run:") . ' ' .
124                    join ", ", @completed
125            });
126        }
127    };
128
129    $unlock->();
130}
131
132sub _lock {
133    my $mgr = shift;
134
135    my $cfg = MT->config;
136
137    # It's unwise to ignore locking for task manager; NoLocking should be
138    # limited to the DBM driver.
139    #if ($cfg->NoLocking) {
140    #    ## If the user doesn't want locking, don't try to lock anything.
141    #    ## Safe for tasks??
142    #    return sub { };
143    #}
144
145    my $temp_dir = $cfg->TempDir;
146    my $mt_dir = MT->instance->{mt_dir};
147    $mt_dir =~ s/[^A-Za-z0-9]+/_/g;
148    my $lock_name = "mt-tasks-$mt_dir.lock";
149    require File::Spec;
150    $lock_name = File::Spec->catfile($temp_dir, $lock_name);
151
152    if ($cfg->UseNFSSafeLocking) {
153        require Sys::Hostname;
154        my $hostname = Sys::Hostname::hostname();
155        my $lock_tmp = $lock_name . '.' . $hostname . '.' . $$;
156        my $max_lock_age = 60;    ## no. of seconds til we break the lock
157        my $tries = 10;           ## no. of seconds to keep trying
158        my $lock_fh = gensym();
159        open $lock_fh, ">$lock_tmp" or return;
160        select((select($lock_fh), $|=1)[0]);  ## Turn off buffering
161        my $got_lock = 0;
162        for (0..$tries-1) {
163            print $lock_fh $$, "\n"; ## Update modified time on lockfile
164            if (link($lock_tmp, $lock_name)) {
165                $got_lock++; last;
166            } elsif ((stat $lock_tmp)[3] > 1) {
167                ## link() failed, but the file exists--we got the lock.
168                $got_lock++; last;
169            } else {
170                ## Couldn't get a lock; if the lock is too old, break it.
171                my $lock_age = (stat $lock_name)[10];
172                unlink $lock_name if time - $lock_age > $max_lock_age;
173            }
174            sleep 1;
175        }
176        close $lock_fh;
177        unlink $lock_tmp;
178        return unless $got_lock;
179        return sub { unlink $lock_name };
180    } else {
181        my $lock_fh = gensym();
182        sysopen $lock_fh, $lock_name, O_RDWR|O_CREAT, 0666
183            or return;
184        my $lock_flags = LOCK_EX;
185        unless (flock $lock_fh, $lock_flags) {
186            close $lock_fh;
187            return;
188        }
189        return sub { close $lock_fh; unlink $lock_name };
190    }
191}
192
1931;
194__END__
195
196=head1 NAME
197
198MT::TaskMgr - MT class for controlling the execution of system tasks.
199
200=head1 SYNOPSIS
201
202    MT::TaskMgr->run_tasks;
203
204=head1 DESCRIPTION
205
206C<MT::TaskMgr> defines a simple framework for the execution of a group of
207runnable tasks (individually declared as C<MT::Task> objects). Each task
208is executed according to their defined frequency. Tasks that fail are logged
209to MT's log table.
210
211=head1 ABOUT TASKS
212
213Movable Type, being a publishing framework, can benefit greatly by having
214a system of tasks that can be run "offline". Unfortunately, many MT users
215don't have the luxury of scheduling these tasks using "cron" or other similar
216facilities some servers provide. To satisfy everyone, the task framework
217introduced here allows MT and third-party plugins to register tasks that
218can be executed whenever the task subsystem is invoked. This can happen
219a number of ways:
220
221=over 4
222
223=item * By a script: tools/run-periodic-tasks
224
225For those that do have a "cron" system, they can continue to run the
226C<tools/run-periodic-tasks> script provided with Movable Type. This script
227now invokes the task subsystem to execute B<all> available tasks instead of
228just the one for publishing scheduled posts.
229
230=item * By fetching an activity feed
231
232With the activity feeds MT serves, it will invoke the task subsystem first,
233then return the feed. This allows users without access to cron service to
234run scheduled tasks. Note however, that this mode is reliant upon the feed
235being pulled by some client. If the feed is not being accessed, then the
236tasks won't run either. A user can utilize a feed-reading online service to
237achieve "24x7" task service to keep their tasks running smoothly.
238
239=item * Other requests
240
241Some tasks, such as the expiration of junk records, may be conditionally
242executed. Junk feedback record expiration is a MT-defined task that executes
243when tasks are run, and also when a new feedback record is scored as junk.
244
245=back
246
247Tasks are an excellent way to maximize MT performance and user experience.
248For example, a plugin that may need to retrieve or synchronize data with a
249remote server may choose to operate from a cache that is periodically kept
250up to date using a registered task.
251
252=head1 METHODS
253
254=head2 MT::TaskMgr->new
255
256Constructs the MT::TaskMgr singleton instance.
257
258=head2 MT::TaskMgr->init
259
260Initializes the MT::TaskMgr instance, pulling tasks are defined in
261the MT registry. It also runs a callback 'tasks' after gathering
262this list.
263
264=head2 MT::TaskMgr->run_tasks
265
266Runs all available pending tasks. If an instance of the TaskMgr is already
267found to be running (through use of a physical file lock mechanism), the
268process will abort.
269
270=head2 MT::TaskMgr->instance
271
272Returns the TaskMgr singleton.
273
274=head1 CALLBACKS
275
276=head2 PeriodicTask
277
278Prior to running any registered tasks, this callback is issued to allow
279any registered MT plugins to add additional tasks to the list or simply
280as a way to signal tasks are about to start. This callback sends no
281parameters, but it is possible to retrieve the active I<MT::TaskMgr>
282instance using the I<instance> method.
283
284=head2 tasks(\%tasks)
285
286Upon initialization of the TaskMgr instance, the list of MT tasks are
287gathered from the MT registry. This hashref of tasks is then passed to
288the 'tasks' callback, giving plugins a chance to manipulate the task
289metadata before being used.
290
291=head1 AUTHOR & COPYRIGHTS
292
293Please see the I<MT> manpage for author, copyright, and license information.
294
295=cut
Note: See TracBrowser for help on using the browser.