root/branches/release-26/lib/MT/TaskMgr.pm @ 1174

Revision 1174, 8.4 kB (checked in by bchoate, 23 months ago)

Updated copyright year for source.

  • 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                });
107            } else {
108                push @completed, $name if (defined $status) && ($status ne '') && ($status > 0);
109 
110            }
111
112            $sess->start(time);
113            $sess->save;
114        }
115        if (@completed) {
116            $app->log({
117                class => 'system',
118                category => 'tasks',
119                level => MT::Log::INFO(),
120                message => $app->translate("Scheduled Tasks Update"),
121                metadata => $app->translate("The following tasks were run:") . ' ' .
122                    join ", ", @completed
123            });
124        }
125    };
126
127    $unlock->();
128}
129
130sub _lock {
131    my $mgr = shift;
132
133    my $cfg = MT->config;
134
135    # It's unwise to ignore locking for task manager; NoLocking should be
136    # limited to the DBM driver.
137    #if ($cfg->NoLocking) {
138    #    ## If the user doesn't want locking, don't try to lock anything.
139    #    ## Safe for tasks??
140    #    return sub { };
141    #}
142
143    my $temp_dir = $cfg->TempDir;
144    my $mt_dir = MT->instance->{mt_dir};
145    $mt_dir =~ s/[^A-Za-z0-9]+/_/g;
146    my $lock_name = "mt-tasks-$mt_dir.lock";
147    require File::Spec;
148    $lock_name = File::Spec->catfile($temp_dir, $lock_name);
149
150    if ($cfg->UseNFSSafeLocking) {
151        require Sys::Hostname;
152        my $hostname = Sys::Hostname::hostname();
153        my $lock_tmp = $lock_name . '.' . $hostname . '.' . $$;
154        my $max_lock_age = 60;    ## no. of seconds til we break the lock
155        my $tries = 10;           ## no. of seconds to keep trying
156        my $lock_fh = gensym();
157        open $lock_fh, ">$lock_tmp" or return;
158        select((select($lock_fh), $|=1)[0]);  ## Turn off buffering
159        my $got_lock = 0;
160        for (0..$tries-1) {
161            print $lock_fh $$, "\n"; ## Update modified time on lockfile
162            if (link($lock_tmp, $lock_name)) {
163                $got_lock++; last;
164            } elsif ((stat $lock_tmp)[3] > 1) {
165                ## link() failed, but the file exists--we got the lock.
166                $got_lock++; last;
167            } else {
168                ## Couldn't get a lock; if the lock is too old, break it.
169                my $lock_age = (stat $lock_name)[10];
170                unlink $lock_name if time - $lock_age > $max_lock_age;
171            }
172            sleep 1;
173        }
174        close $lock_fh;
175        unlink $lock_tmp;
176        return unless $got_lock;
177        return sub { unlink $lock_name };
178    } else {
179        my $lock_fh = gensym();
180        sysopen $lock_fh, $lock_name, O_RDWR|O_CREAT, 0666
181            or return;
182        my $lock_flags = LOCK_EX;
183        unless (flock $lock_fh, $lock_flags) {
184            close $lock_fh;
185            return;
186        }
187        return sub { close $lock_fh; unlink $lock_name };
188    }
189}
190
1911;
192__END__
193
194=head1 NAME
195
196MT::TaskMgr - MT class for controlling the execution of system tasks.
197
198=head1 SYNOPSIS
199
200    MT::TaskMgr->add_task($task);
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->add_task($task_obj)
255
256=head2 MT::TaskMgr->add_task(\%task)
257
258Registers a new I<MT::Task> object. If this method is called with a hashref,
259a I<MT::Task> will be constructed using that data.
260
261=head2 MT::TaskMgr->run_tasks
262
263Runs all available pending tasks. If an instance of the TaskMgr is already
264found to be running (through use of a physical file lock mechanism), the
265process will abort.
266
267=head2 MT::TaskMgr->instance
268
269Returns the TaskMgr singleton.
270
271=head1 CALLBACKS
272
273=head2 PeriodicTask
274
275Prior to running any registered tasks, this callback is issued to allow
276any registered MT plugins to add additional tasks to the list or simply
277as a way to signal tasks are about to start. This callback sends no
278parameters, but it is possible to retrieve the active I<MT::TaskMgr>
279instance using the I<instance> method.
280
281=head1 AUTHOR & COPYRIGHTS
282
283Please see the I<MT> manpage for author, copyright, and license information.
284
285=cut
Note: See TracBrowser for help on using the browser.