1
0
Fork 0
mirror of https://we.phorge.it/source/phorge.git synced 2024-12-01 11:12:42 +01:00
phorge-phorge/src/applications/daemon/controller/PhabricatorDaemonConsoleController.php

270 lines
7.6 KiB
PHP
Raw Normal View History

<?php
final class PhabricatorDaemonConsoleController
extends PhabricatorDaemonController {
public function handleRequest(AphrontRequest $request) {
$viewer = $this->getViewer();
$window_start = (time() - (60 * 15));
// Assume daemons spend about 250ms second in overhead per task acquiring
// leases and doing other bookkeeping. This is probably an over-estimation,
// but we'd rather show that utilization is too high than too low.
$lease_overhead = 0.250;
$completed = id(new PhabricatorWorkerArchiveTaskQuery())
->withDateModifiedSince($window_start)
->execute();
$failed = id(new PhabricatorWorkerActiveTask())->loadAllWhere(
'failureTime > %d',
$window_start);
$usage_total = 0;
$usage_start = PHP_INT_MAX;
$completed_info = array();
foreach ($completed as $completed_task) {
$class = $completed_task->getTaskClass();
if (empty($completed_info[$class])) {
$completed_info[$class] = array(
'n' => 0,
'duration' => 0,
);
}
$completed_info[$class]['n']++;
$duration = $completed_task->getDuration();
$completed_info[$class]['duration'] += $duration;
// NOTE: Duration is in microseconds, but we're just using seconds to
// compute utilization.
$usage_total += $lease_overhead + ($duration / 1000000);
$usage_start = min($usage_start, $completed_task->getDateModified());
}
$completed_info = isort($completed_info, 'n');
$rows = array();
foreach ($completed_info as $class => $info) {
$rows[] = array(
2013-02-13 23:50:15 +01:00
$class,
number_format($info['n']),
pht('%s us', new PhutilNumber((int)($info['duration'] / $info['n']))),
);
}
if ($failed) {
// Add the time it takes to restart the daemons. This includes a guess
// about other overhead of 2X.
$restart_delay = PhutilDaemonHandle::getWaitBeforeRestart();
$usage_total += $restart_delay * count($failed) * 2;
foreach ($failed as $failed_task) {
$usage_start = min($usage_start, $failed_task->getFailureTime());
}
$rows[] = array(
phutil_tag('em', array(), pht('Temporary Failures')),
count($failed),
null,
);
}
$logs = id(new PhabricatorDaemonLogQuery())
->setViewer($viewer)
->withStatus(PhabricatorDaemonLogQuery::STATUS_ALIVE)
->setAllowStatusWrites(true)
->execute();
$taskmasters = 0;
foreach ($logs as $log) {
if ($log->getDaemon() == 'PhabricatorTaskmasterDaemon') {
$taskmasters++;
}
}
if ($taskmasters && $usage_total) {
// Total number of wall-time seconds the daemons have been running since
// the oldest event. For very short times round up to 15s so we don't
// render any ridiculous numbers if you reload the page immediately after
// restarting the daemons.
$available_time = $taskmasters * max(15, (time() - $usage_start));
// Percentage of those wall-time seconds we can account for, which the
// daemons spent doing work:
$used_time = ($usage_total / $available_time);
$rows[] = array(
phutil_tag('em', array(), pht('Queue Utilization (Approximate)')),
sprintf('%.1f%%', 100 * $used_time),
null,
);
}
$completed_table = new AphrontTableView($rows);
$completed_table->setNoDataString(
pht('No tasks have completed in the last 15 minutes.'));
$completed_table->setHeaders(
array(
pht('Class'),
pht('Count'),
pht('Avg'),
));
$completed_table->setColumnClasses(
array(
'wide',
'n',
'n',
));
$completed_panel = id(new PHUIObjectBoxView())
->setHeaderText(pht('Recently Completed Tasks (Last 15m)'))
->setTable($completed_table);
$daemon_table = id(new PhabricatorDaemonLogListView())
->setUser($viewer)
->setDaemonLogs($logs);
$daemon_panel = id(new PHUIObjectBoxView())
->setHeaderText(pht('Active Daemons'))
->setTable($daemon_table);
$tasks = id(new PhabricatorWorkerLeaseQuery())
->setSkipLease(true)
->withLeasedTasks(true)
->setLimit(100)
->execute();
$tasks_table = id(new PhabricatorDaemonTasksTableView())
->setTasks($tasks)
->setNoDataString(pht('No tasks are leased by workers.'));
$leased_panel = id(new PHUIObjectBoxView())
->setHeaderText(pht('Leased Tasks'))
->setTable($tasks_table);
$task_table = new PhabricatorWorkerActiveTask();
$queued = queryfx_all(
$task_table->establishConnection('r'),
'SELECT taskClass, count(*) N FROM %T GROUP BY taskClass
ORDER BY N DESC',
$task_table->getTableName());
$rows = array();
foreach ($queued as $row) {
$rows[] = array(
2013-02-13 23:50:15 +01:00
$row['taskClass'],
number_format($row['N']),
);
}
$queued_table = new AphrontTableView($rows);
$queued_table->setHeaders(
array(
pht('Class'),
pht('Count'),
));
$queued_table->setColumnClasses(
array(
'wide',
'n',
));
$queued_table->setNoDataString(pht('Task queue is empty.'));
$queued_panel = new PHUIObjectBoxView();
$queued_panel->setHeaderText(pht('Queued Tasks'));
$queued_panel->setTable($queued_table);
$upcoming = id(new PhabricatorWorkerLeaseQuery())
->setLimit(10)
->setSkipLease(true)
->execute();
$upcoming_panel = id(new PHUIObjectBoxView())
->setHeaderText(pht('Next In Queue'))
->setTable(
id(new PhabricatorDaemonTasksTableView())
->setTasks($upcoming)
->setNoDataString(pht('Task queue is empty.')));
Implement clock/trigger infrastructure for scheduling actions Summary: Ref T6881. Hopefully, this is the hard part. This adds a new daemon (the "trigger" daemon) which processes triggers, schedules them, and then executes them at the scheduled time. The design is a little complicated, but has these goals: - High resistance to race conditions: only the application writes to the trigger table; only the daemon writes to the event table. We won't lose events if someone saves a meeting at the same time as we're sending a reminder out for it. - Execution guarantees: scheduled events are guaranteed to execute exactly once. - Support for arbitrarily large queues: the daemon will make progress even if there are millions of triggers in queue. The cost to update the queue is proportional to the number of changes in it; the cost to process the queue is proportional to the number of events to execute. - Relatively good observability: you can monitor the state of the trigger queue reasonably well from the web UI. - Modular Infrastructure: this is a very low-level construct that Calendar, Phortune, etc., should be able to build on top of. It doesn't have this stuff yet: - Not very robust to bad actions: a misbehaving trigger can stop the queue fairly easily. This is OK for now since we aren't planning to make it part of any other applications for a while. We do still get execute-exaclty-once, but it might not happen for a long time (until someone goes and fixes the queue), when we could theoretically continue executing other events. - Doesn't start automatically: normal users don't need to run this thing yet so I'm not starting it by default. - Not super well tested: I've vetted the basics but haven't run real workloads through this yet. - No sophisticated tooling: I added some basic stuff but it's missing some pieces we'll have to build sooner or later, e.g. `bin/trigger cancel` or whatever. - Intentionally not realtime: This design puts execution guarantees far above realtime concerns, and will not give you precise event execution at 1-second resolution. I think this is the correct goal to pursue architecturally, and certainly correct for subscriptions and meeting reminders. Events which execute after they have become irrelevant can simply decline to do anything (like a meeting reminder which executes after the meeting is over). In general, the expectation for applications is: - When creating an object (like a calendar event) that needs to trigger a scheduled action, write a trigger (and save the PHID if you plan to update it later). - The daemon will process the event and schedule the action efficiently, in a race-free way. - If you want to move the action, update the trigger and the daemon will take care of it. - Your action will eventually dump a task into the task queue, and the task daemons will actually perform it. Test Plan: Using a test script like this: ``` <?php require_once 'scripts/__init_script__.php'; $trigger = id(new PhabricatorWorkerTrigger()) ->setAction( new PhabricatorLogTriggerAction( array( 'message' => 'test', ))) ->setClock( new PhabricatorMetronomicTriggerClock( array( 'period' => 33, ))) ->save(); var_dump($trigger); ``` ...I queued triggers and ran the daemon: - Verified triggers fire; - verified triggers reschedule; - verified trigger events show up in the web UI; - tried different periods; - added some triggers while the daemon was running; - examined `phd debug` output for anything suspicious. It seems to work in trivial use case, at least. Reviewers: btrahan Reviewed By: btrahan Subscribers: epriestley Maniphest Tasks: T6881 Differential Revision: https://secure.phabricator.com/D11419
2015-01-16 21:13:31 +01:00
$triggers = id(new PhabricatorWorkerTriggerQuery())
->setViewer($viewer)
Implement clock/trigger infrastructure for scheduling actions Summary: Ref T6881. Hopefully, this is the hard part. This adds a new daemon (the "trigger" daemon) which processes triggers, schedules them, and then executes them at the scheduled time. The design is a little complicated, but has these goals: - High resistance to race conditions: only the application writes to the trigger table; only the daemon writes to the event table. We won't lose events if someone saves a meeting at the same time as we're sending a reminder out for it. - Execution guarantees: scheduled events are guaranteed to execute exactly once. - Support for arbitrarily large queues: the daemon will make progress even if there are millions of triggers in queue. The cost to update the queue is proportional to the number of changes in it; the cost to process the queue is proportional to the number of events to execute. - Relatively good observability: you can monitor the state of the trigger queue reasonably well from the web UI. - Modular Infrastructure: this is a very low-level construct that Calendar, Phortune, etc., should be able to build on top of. It doesn't have this stuff yet: - Not very robust to bad actions: a misbehaving trigger can stop the queue fairly easily. This is OK for now since we aren't planning to make it part of any other applications for a while. We do still get execute-exaclty-once, but it might not happen for a long time (until someone goes and fixes the queue), when we could theoretically continue executing other events. - Doesn't start automatically: normal users don't need to run this thing yet so I'm not starting it by default. - Not super well tested: I've vetted the basics but haven't run real workloads through this yet. - No sophisticated tooling: I added some basic stuff but it's missing some pieces we'll have to build sooner or later, e.g. `bin/trigger cancel` or whatever. - Intentionally not realtime: This design puts execution guarantees far above realtime concerns, and will not give you precise event execution at 1-second resolution. I think this is the correct goal to pursue architecturally, and certainly correct for subscriptions and meeting reminders. Events which execute after they have become irrelevant can simply decline to do anything (like a meeting reminder which executes after the meeting is over). In general, the expectation for applications is: - When creating an object (like a calendar event) that needs to trigger a scheduled action, write a trigger (and save the PHID if you plan to update it later). - The daemon will process the event and schedule the action efficiently, in a race-free way. - If you want to move the action, update the trigger and the daemon will take care of it. - Your action will eventually dump a task into the task queue, and the task daemons will actually perform it. Test Plan: Using a test script like this: ``` <?php require_once 'scripts/__init_script__.php'; $trigger = id(new PhabricatorWorkerTrigger()) ->setAction( new PhabricatorLogTriggerAction( array( 'message' => 'test', ))) ->setClock( new PhabricatorMetronomicTriggerClock( array( 'period' => 33, ))) ->save(); var_dump($trigger); ``` ...I queued triggers and ran the daemon: - Verified triggers fire; - verified triggers reschedule; - verified trigger events show up in the web UI; - tried different periods; - added some triggers while the daemon was running; - examined `phd debug` output for anything suspicious. It seems to work in trivial use case, at least. Reviewers: btrahan Reviewed By: btrahan Subscribers: epriestley Maniphest Tasks: T6881 Differential Revision: https://secure.phabricator.com/D11419
2015-01-16 21:13:31 +01:00
->setOrder(PhabricatorWorkerTriggerQuery::ORDER_EXECUTION)
->withNextEventBetween(0, null)
Implement clock/trigger infrastructure for scheduling actions Summary: Ref T6881. Hopefully, this is the hard part. This adds a new daemon (the "trigger" daemon) which processes triggers, schedules them, and then executes them at the scheduled time. The design is a little complicated, but has these goals: - High resistance to race conditions: only the application writes to the trigger table; only the daemon writes to the event table. We won't lose events if someone saves a meeting at the same time as we're sending a reminder out for it. - Execution guarantees: scheduled events are guaranteed to execute exactly once. - Support for arbitrarily large queues: the daemon will make progress even if there are millions of triggers in queue. The cost to update the queue is proportional to the number of changes in it; the cost to process the queue is proportional to the number of events to execute. - Relatively good observability: you can monitor the state of the trigger queue reasonably well from the web UI. - Modular Infrastructure: this is a very low-level construct that Calendar, Phortune, etc., should be able to build on top of. It doesn't have this stuff yet: - Not very robust to bad actions: a misbehaving trigger can stop the queue fairly easily. This is OK for now since we aren't planning to make it part of any other applications for a while. We do still get execute-exaclty-once, but it might not happen for a long time (until someone goes and fixes the queue), when we could theoretically continue executing other events. - Doesn't start automatically: normal users don't need to run this thing yet so I'm not starting it by default. - Not super well tested: I've vetted the basics but haven't run real workloads through this yet. - No sophisticated tooling: I added some basic stuff but it's missing some pieces we'll have to build sooner or later, e.g. `bin/trigger cancel` or whatever. - Intentionally not realtime: This design puts execution guarantees far above realtime concerns, and will not give you precise event execution at 1-second resolution. I think this is the correct goal to pursue architecturally, and certainly correct for subscriptions and meeting reminders. Events which execute after they have become irrelevant can simply decline to do anything (like a meeting reminder which executes after the meeting is over). In general, the expectation for applications is: - When creating an object (like a calendar event) that needs to trigger a scheduled action, write a trigger (and save the PHID if you plan to update it later). - The daemon will process the event and schedule the action efficiently, in a race-free way. - If you want to move the action, update the trigger and the daemon will take care of it. - Your action will eventually dump a task into the task queue, and the task daemons will actually perform it. Test Plan: Using a test script like this: ``` <?php require_once 'scripts/__init_script__.php'; $trigger = id(new PhabricatorWorkerTrigger()) ->setAction( new PhabricatorLogTriggerAction( array( 'message' => 'test', ))) ->setClock( new PhabricatorMetronomicTriggerClock( array( 'period' => 33, ))) ->save(); var_dump($trigger); ``` ...I queued triggers and ran the daemon: - Verified triggers fire; - verified triggers reschedule; - verified trigger events show up in the web UI; - tried different periods; - added some triggers while the daemon was running; - examined `phd debug` output for anything suspicious. It seems to work in trivial use case, at least. Reviewers: btrahan Reviewed By: btrahan Subscribers: epriestley Maniphest Tasks: T6881 Differential Revision: https://secure.phabricator.com/D11419
2015-01-16 21:13:31 +01:00
->needEvents(true)
->setLimit(10)
->execute();
$triggers_table = $this->buildTriggersTable($triggers);
$triggers_panel = id(new PHUIObjectBoxView())
->setHeaderText(pht('Upcoming Triggers'))
->setTable($triggers_table);
Implement clock/trigger infrastructure for scheduling actions Summary: Ref T6881. Hopefully, this is the hard part. This adds a new daemon (the "trigger" daemon) which processes triggers, schedules them, and then executes them at the scheduled time. The design is a little complicated, but has these goals: - High resistance to race conditions: only the application writes to the trigger table; only the daemon writes to the event table. We won't lose events if someone saves a meeting at the same time as we're sending a reminder out for it. - Execution guarantees: scheduled events are guaranteed to execute exactly once. - Support for arbitrarily large queues: the daemon will make progress even if there are millions of triggers in queue. The cost to update the queue is proportional to the number of changes in it; the cost to process the queue is proportional to the number of events to execute. - Relatively good observability: you can monitor the state of the trigger queue reasonably well from the web UI. - Modular Infrastructure: this is a very low-level construct that Calendar, Phortune, etc., should be able to build on top of. It doesn't have this stuff yet: - Not very robust to bad actions: a misbehaving trigger can stop the queue fairly easily. This is OK for now since we aren't planning to make it part of any other applications for a while. We do still get execute-exaclty-once, but it might not happen for a long time (until someone goes and fixes the queue), when we could theoretically continue executing other events. - Doesn't start automatically: normal users don't need to run this thing yet so I'm not starting it by default. - Not super well tested: I've vetted the basics but haven't run real workloads through this yet. - No sophisticated tooling: I added some basic stuff but it's missing some pieces we'll have to build sooner or later, e.g. `bin/trigger cancel` or whatever. - Intentionally not realtime: This design puts execution guarantees far above realtime concerns, and will not give you precise event execution at 1-second resolution. I think this is the correct goal to pursue architecturally, and certainly correct for subscriptions and meeting reminders. Events which execute after they have become irrelevant can simply decline to do anything (like a meeting reminder which executes after the meeting is over). In general, the expectation for applications is: - When creating an object (like a calendar event) that needs to trigger a scheduled action, write a trigger (and save the PHID if you plan to update it later). - The daemon will process the event and schedule the action efficiently, in a race-free way. - If you want to move the action, update the trigger and the daemon will take care of it. - Your action will eventually dump a task into the task queue, and the task daemons will actually perform it. Test Plan: Using a test script like this: ``` <?php require_once 'scripts/__init_script__.php'; $trigger = id(new PhabricatorWorkerTrigger()) ->setAction( new PhabricatorLogTriggerAction( array( 'message' => 'test', ))) ->setClock( new PhabricatorMetronomicTriggerClock( array( 'period' => 33, ))) ->save(); var_dump($trigger); ``` ...I queued triggers and ran the daemon: - Verified triggers fire; - verified triggers reschedule; - verified trigger events show up in the web UI; - tried different periods; - added some triggers while the daemon was running; - examined `phd debug` output for anything suspicious. It seems to work in trivial use case, at least. Reviewers: btrahan Reviewed By: btrahan Subscribers: epriestley Maniphest Tasks: T6881 Differential Revision: https://secure.phabricator.com/D11419
2015-01-16 21:13:31 +01:00
$crumbs = $this->buildApplicationCrumbs();
$crumbs->addTextCrumb(pht('Console'));
$nav = $this->buildSideNavView();
$nav->selectFilter('/');
$nav->appendChild(
array(
$crumbs,
$completed_panel,
$daemon_panel,
$queued_panel,
$leased_panel,
$upcoming_panel,
Implement clock/trigger infrastructure for scheduling actions Summary: Ref T6881. Hopefully, this is the hard part. This adds a new daemon (the "trigger" daemon) which processes triggers, schedules them, and then executes them at the scheduled time. The design is a little complicated, but has these goals: - High resistance to race conditions: only the application writes to the trigger table; only the daemon writes to the event table. We won't lose events if someone saves a meeting at the same time as we're sending a reminder out for it. - Execution guarantees: scheduled events are guaranteed to execute exactly once. - Support for arbitrarily large queues: the daemon will make progress even if there are millions of triggers in queue. The cost to update the queue is proportional to the number of changes in it; the cost to process the queue is proportional to the number of events to execute. - Relatively good observability: you can monitor the state of the trigger queue reasonably well from the web UI. - Modular Infrastructure: this is a very low-level construct that Calendar, Phortune, etc., should be able to build on top of. It doesn't have this stuff yet: - Not very robust to bad actions: a misbehaving trigger can stop the queue fairly easily. This is OK for now since we aren't planning to make it part of any other applications for a while. We do still get execute-exaclty-once, but it might not happen for a long time (until someone goes and fixes the queue), when we could theoretically continue executing other events. - Doesn't start automatically: normal users don't need to run this thing yet so I'm not starting it by default. - Not super well tested: I've vetted the basics but haven't run real workloads through this yet. - No sophisticated tooling: I added some basic stuff but it's missing some pieces we'll have to build sooner or later, e.g. `bin/trigger cancel` or whatever. - Intentionally not realtime: This design puts execution guarantees far above realtime concerns, and will not give you precise event execution at 1-second resolution. I think this is the correct goal to pursue architecturally, and certainly correct for subscriptions and meeting reminders. Events which execute after they have become irrelevant can simply decline to do anything (like a meeting reminder which executes after the meeting is over). In general, the expectation for applications is: - When creating an object (like a calendar event) that needs to trigger a scheduled action, write a trigger (and save the PHID if you plan to update it later). - The daemon will process the event and schedule the action efficiently, in a race-free way. - If you want to move the action, update the trigger and the daemon will take care of it. - Your action will eventually dump a task into the task queue, and the task daemons will actually perform it. Test Plan: Using a test script like this: ``` <?php require_once 'scripts/__init_script__.php'; $trigger = id(new PhabricatorWorkerTrigger()) ->setAction( new PhabricatorLogTriggerAction( array( 'message' => 'test', ))) ->setClock( new PhabricatorMetronomicTriggerClock( array( 'period' => 33, ))) ->save(); var_dump($trigger); ``` ...I queued triggers and ran the daemon: - Verified triggers fire; - verified triggers reschedule; - verified trigger events show up in the web UI; - tried different periods; - added some triggers while the daemon was running; - examined `phd debug` output for anything suspicious. It seems to work in trivial use case, at least. Reviewers: btrahan Reviewed By: btrahan Subscribers: epriestley Maniphest Tasks: T6881 Differential Revision: https://secure.phabricator.com/D11419
2015-01-16 21:13:31 +01:00
$triggers_panel,
));
return $this->newPage()
->setTitle(pht('Console'))
->appendChild($nav);
}
Implement clock/trigger infrastructure for scheduling actions Summary: Ref T6881. Hopefully, this is the hard part. This adds a new daemon (the "trigger" daemon) which processes triggers, schedules them, and then executes them at the scheduled time. The design is a little complicated, but has these goals: - High resistance to race conditions: only the application writes to the trigger table; only the daemon writes to the event table. We won't lose events if someone saves a meeting at the same time as we're sending a reminder out for it. - Execution guarantees: scheduled events are guaranteed to execute exactly once. - Support for arbitrarily large queues: the daemon will make progress even if there are millions of triggers in queue. The cost to update the queue is proportional to the number of changes in it; the cost to process the queue is proportional to the number of events to execute. - Relatively good observability: you can monitor the state of the trigger queue reasonably well from the web UI. - Modular Infrastructure: this is a very low-level construct that Calendar, Phortune, etc., should be able to build on top of. It doesn't have this stuff yet: - Not very robust to bad actions: a misbehaving trigger can stop the queue fairly easily. This is OK for now since we aren't planning to make it part of any other applications for a while. We do still get execute-exaclty-once, but it might not happen for a long time (until someone goes and fixes the queue), when we could theoretically continue executing other events. - Doesn't start automatically: normal users don't need to run this thing yet so I'm not starting it by default. - Not super well tested: I've vetted the basics but haven't run real workloads through this yet. - No sophisticated tooling: I added some basic stuff but it's missing some pieces we'll have to build sooner or later, e.g. `bin/trigger cancel` or whatever. - Intentionally not realtime: This design puts execution guarantees far above realtime concerns, and will not give you precise event execution at 1-second resolution. I think this is the correct goal to pursue architecturally, and certainly correct for subscriptions and meeting reminders. Events which execute after they have become irrelevant can simply decline to do anything (like a meeting reminder which executes after the meeting is over). In general, the expectation for applications is: - When creating an object (like a calendar event) that needs to trigger a scheduled action, write a trigger (and save the PHID if you plan to update it later). - The daemon will process the event and schedule the action efficiently, in a race-free way. - If you want to move the action, update the trigger and the daemon will take care of it. - Your action will eventually dump a task into the task queue, and the task daemons will actually perform it. Test Plan: Using a test script like this: ``` <?php require_once 'scripts/__init_script__.php'; $trigger = id(new PhabricatorWorkerTrigger()) ->setAction( new PhabricatorLogTriggerAction( array( 'message' => 'test', ))) ->setClock( new PhabricatorMetronomicTriggerClock( array( 'period' => 33, ))) ->save(); var_dump($trigger); ``` ...I queued triggers and ran the daemon: - Verified triggers fire; - verified triggers reschedule; - verified trigger events show up in the web UI; - tried different periods; - added some triggers while the daemon was running; - examined `phd debug` output for anything suspicious. It seems to work in trivial use case, at least. Reviewers: btrahan Reviewed By: btrahan Subscribers: epriestley Maniphest Tasks: T6881 Differential Revision: https://secure.phabricator.com/D11419
2015-01-16 21:13:31 +01:00
private function buildTriggersTable(array $triggers) {
$viewer = $this->getViewer();
$rows = array();
foreach ($triggers as $trigger) {
$event = $trigger->getEvent();
if ($event) {
$last_epoch = $event->getLastEventEpoch();
$next_epoch = $event->getNextEventEpoch();
} else {
$last_epoch = null;
$next_epoch = null;
}
$rows[] = array(
$trigger->getID(),
$trigger->getClockClass(),
$trigger->getActionClass(),
$last_epoch ? phabricator_datetime($last_epoch, $viewer) : null,
$next_epoch ? phabricator_datetime($next_epoch, $viewer) : null,
);
}
return id(new AphrontTableView($rows))
->setNoDataString(pht('There are no upcoming event triggers.'))
->setHeaders(
array(
pht('ID'),
pht('Clock'),
pht('Action'),
pht('Last'),
pht('Next'),
Implement clock/trigger infrastructure for scheduling actions Summary: Ref T6881. Hopefully, this is the hard part. This adds a new daemon (the "trigger" daemon) which processes triggers, schedules them, and then executes them at the scheduled time. The design is a little complicated, but has these goals: - High resistance to race conditions: only the application writes to the trigger table; only the daemon writes to the event table. We won't lose events if someone saves a meeting at the same time as we're sending a reminder out for it. - Execution guarantees: scheduled events are guaranteed to execute exactly once. - Support for arbitrarily large queues: the daemon will make progress even if there are millions of triggers in queue. The cost to update the queue is proportional to the number of changes in it; the cost to process the queue is proportional to the number of events to execute. - Relatively good observability: you can monitor the state of the trigger queue reasonably well from the web UI. - Modular Infrastructure: this is a very low-level construct that Calendar, Phortune, etc., should be able to build on top of. It doesn't have this stuff yet: - Not very robust to bad actions: a misbehaving trigger can stop the queue fairly easily. This is OK for now since we aren't planning to make it part of any other applications for a while. We do still get execute-exaclty-once, but it might not happen for a long time (until someone goes and fixes the queue), when we could theoretically continue executing other events. - Doesn't start automatically: normal users don't need to run this thing yet so I'm not starting it by default. - Not super well tested: I've vetted the basics but haven't run real workloads through this yet. - No sophisticated tooling: I added some basic stuff but it's missing some pieces we'll have to build sooner or later, e.g. `bin/trigger cancel` or whatever. - Intentionally not realtime: This design puts execution guarantees far above realtime concerns, and will not give you precise event execution at 1-second resolution. I think this is the correct goal to pursue architecturally, and certainly correct for subscriptions and meeting reminders. Events which execute after they have become irrelevant can simply decline to do anything (like a meeting reminder which executes after the meeting is over). In general, the expectation for applications is: - When creating an object (like a calendar event) that needs to trigger a scheduled action, write a trigger (and save the PHID if you plan to update it later). - The daemon will process the event and schedule the action efficiently, in a race-free way. - If you want to move the action, update the trigger and the daemon will take care of it. - Your action will eventually dump a task into the task queue, and the task daemons will actually perform it. Test Plan: Using a test script like this: ``` <?php require_once 'scripts/__init_script__.php'; $trigger = id(new PhabricatorWorkerTrigger()) ->setAction( new PhabricatorLogTriggerAction( array( 'message' => 'test', ))) ->setClock( new PhabricatorMetronomicTriggerClock( array( 'period' => 33, ))) ->save(); var_dump($trigger); ``` ...I queued triggers and ran the daemon: - Verified triggers fire; - verified triggers reschedule; - verified trigger events show up in the web UI; - tried different periods; - added some triggers while the daemon was running; - examined `phd debug` output for anything suspicious. It seems to work in trivial use case, at least. Reviewers: btrahan Reviewed By: btrahan Subscribers: epriestley Maniphest Tasks: T6881 Differential Revision: https://secure.phabricator.com/D11419
2015-01-16 21:13:31 +01:00
))
->setColumnClasses(
array(
'',
'',
'wide',
'date',
'date',
));
}
}