root/branches/release-35/lib/MT/TaskMgr.pm @ 1912

Revision 1912, 8.6 kB (checked in by fumiakiy, 20 months ago)

Added timestamp to log metadata when either periodic tasks finished, or TheSchwartz published or sync'ed files. BugId:66799

  • 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->add_task($task);
203
204    MT::TaskMgr->run_tasks;
205
206=head1 DESCRIPTION
207
208C<MT::TaskMgr> defines a simple framework for the execution of a group of
209runnable tasks (individually declared as C<MT::Task> objects). Each task
210is executed according to their defined frequency. Tasks that fail are logged
211to MT's log table.
212
213=head1 ABOUT TASKS
214
215Movable Type, being a publishing framework, can benefit greatly by having
216a system of tasks that can be run "offline". Unfortunately, many MT users
217don't have the luxury of scheduling these tasks using "cron" or other similar
218facilities some servers provide. To satisfy everyone, the task framework
219introduced here allows MT and third-party plugins to register tasks that
220can be executed whenever the task subsystem is invoked. This can happen
221a number of ways:
222
223=over 4
224
225=item * By a script: tools/run-periodic-tasks
226
227For those that do have a "cron" system, they can continue to run the
228C<tools/run-periodic-tasks> script provided with Movable Type. This script
229now invokes the task subsystem to execute B<all> available tasks instead of
230just the one for publishing scheduled posts.
231
232=item * By fetching an activity feed
233
234With the activity feeds MT serves, it will invoke the task subsystem first,
235then return the feed. This allows users without access to cron service to
236run scheduled tasks. Note however, that this mode is reliant upon the feed
237being pulled by some client. If the feed is not being accessed, then the
238tasks won't run either. A user can utilize a feed-reading online service to
239achieve "24x7" task service to keep their tasks running smoothly.
240
241=item * Other requests
242
243Some tasks, such as the expiration of junk records, may be conditionally
244executed. Junk feedback record expiration is a MT-defined task that executes
245when tasks are run, and also when a new feedback record is scored as junk.
246
247=back
248
249Tasks are an excellent way to maximize MT performance and user experience.
250For example, a plugin that may need to retrieve or synchronize data with a
251remote server may choose to operate from a cache that is periodically kept
252up to date using a registered task.
253
254=head1 METHODS
255
256=head2 MT::TaskMgr->add_task($task_obj)
257
258=head2 MT::TaskMgr->add_task(\%task)
259
260Registers a new I<MT::Task> object. If this method is called with a hashref,
261a I<MT::Task> will be constructed using that data.
262
263=head2 MT::TaskMgr->run_tasks
264
265Runs all available pending tasks. If an instance of the TaskMgr is already
266found to be running (through use of a physical file lock mechanism), the
267process will abort.
268
269=head2 MT::TaskMgr->instance
270
271Returns the TaskMgr singleton.
272
273=head1 CALLBACKS
274
275=head2 PeriodicTask
276
277Prior to running any registered tasks, this callback is issued to allow
278any registered MT plugins to add additional tasks to the list or simply
279as a way to signal tasks are about to start. This callback sends no
280parameters, but it is possible to retrieve the active I<MT::TaskMgr>
281instance using the I<instance> method.
282
283=head1 AUTHOR & COPYRIGHTS
284
285Please see the I<MT> manpage for author, copyright, and license information.
286
287=cut
Note: See TracBrowser for help on using the browser.