Skip to content

Commit a891fb5

Browse files
committed
2 parents 4d5fc84 + 2aa301e commit a891fb5

2 files changed

Lines changed: 157 additions & 96 deletions

File tree

README.md

Lines changed: 130 additions & 96 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,26 @@
11
# Tasks Event Gateway (TEG)
22

3-
This extension adds the ability to run self-configured Tasks when installed into Lucee Server.
3+
The Tasks Event Gateway extension adds long-running background tasks to Lucee Server. Unlike cron-style schedulers that fire at fixed intervals, each task runs in its own thread loop and controls its own timing — sleep before/after execution, backoff on errors, and optional concurrency.
4+
5+
Use it when you need always-on workers (queue consumers, polling loops, sync jobs) rather than calendar-based scheduling. For cron expressions and one-shot jobs, see the [Quartz Scheduler extension](https://docs.lucee.org/recipes/scheduler-quartz.html).
6+
7+
## Requirements
8+
9+
- Lucee Server (single- or multi-context)
10+
- A configured gateway instance (not created automatically since extension 1.1.0.0)
11+
12+
## Quick start
13+
14+
1. Install the extension (see [Installation](#installation)).
15+
2. Register a gateway instance in `.CFConfig.json` or the Lucee Administrator (see [Configuration](#configuration)).
16+
3. Create a CFC in your configured package that extends `org.lucee.cfml.Task` and implements `invoke()`.
17+
4. Start (or restart) the gateway instance and check the configured log file for `Tasks Event Gateway:` entries.
418

519
## Build
620

721
The extension is a Maven project (which invokes Ant internally). To build it, you need Maven installed on your machine. Once available, simply run:
822

9-
```
23+
```bash
1024
mvn package
1125
```
1226

@@ -16,15 +30,24 @@ within the project root. The resulting `.lex` file will be created in the `targe
1630
1731
## Installation
1832

19-
Copy the generated `.lex` file to the `/lucee-server/deploy` folder of your Lucee installation.
33+
Install via the Lucee Administrator (**Extensions → Available**), or use any method described in the [Extension Installation recipe](https://docs.lucee.org/recipes/extension-installation.html).
34+
35+
Manual deploy: copy the generated `.lex` file to the `/lucee-server/deploy` folder of your Lucee installation. Lucee detects and installs it within about a minute.
36+
37+
- **Maven GAV:** `org.lucee:tasks-extension`
38+
- **Extension ID:** `947C02B0-7AE4-4054-938A8E059DD7625A`
39+
- **Source:** [github.com/lucee/task-event-gateway](https://github.com/lucee/task-event-gateway)
40+
- **Downloads:** [download.lucee.org](https://download.lucee.org/#947C02B0-7AE4-4054-938A8E059DD7625A)
2041

2142
## Configuration
2243

23-
Configuration happens in two places: the gateway instance configuration for global settings, and each Task/Listener implementation for Task/Listener-specific settings.
44+
Configuration happens in two places: the gateway instance configuration for global settings, and each Task/Listener implementation for task-specific settings.
45+
46+
You can run multiple gateway instances (each with its own ID, package, and log). Use a shared cache in `settingLocation` when several servers need to share pause/resume state.
2447

25-
### Gateway Instance Configuration
48+
### Gateway instance configuration
2649

27-
> **Changed in 1.1.0.0:** The extension no longer installs a gateway instance automatically. You must configure the gateway instance manually — either via `.CFConfig.json` or through the Lucee Administrator.
50+
> **Changed in 1.1.0.0:** The extension no longer installs a gateway instance automatically. You must configure the gateway instance manually — either via `.CFConfig.json` or through **Services → Event Gateways** in the Lucee Administrator.
2851
2952
To register a gateway instance via `.CFConfig.json`, add an entry under the `gateways` key:
3053

@@ -38,6 +61,7 @@ To register a gateway instance via `.CFConfig.json`, add an entry under the `gat
3861
"custom": {
3962
"package": "core.tasks",
4063
"templatePath": "/cron",
64+
"templatePathRecursive": true,
4165
"checkForChangeInterval": 10,
4266
"checkForChangeNoMatchInterval": 60,
4367
"settingLocation": "",
@@ -50,89 +74,121 @@ To register a gateway instance via `.CFConfig.json`, add an entry under the `gat
5074
}
5175
```
5276

53-
The `custom` fields control the TEG behaviour:
77+
In the Administrator, choose gateway type **Tasks**, set the instance ID (e.g. `my-task`), and fill in the same custom fields.
78+
79+
The `custom` fields control TEG behaviour:
5480

5581
| Setting | Description |
5682
|---|---|
57-
| `package` | The component package the TEG scans for Task/Listener CFCs (e.g. `core.tasks` maps to `/core/tasks`) |
83+
| `package` | Component package the TEG scans for Task/Listener CFCs (e.g. `core.tasks` maps to `/core/tasks`) |
5884
| `templatePath` | Absolute path, mapping, or webroot-relative path to a folder containing `.cfm`-based tasks |
59-
| `checkForChangeInterval` | Seconds between file-change checks for templates previously identified as tasks. Higher values reduce I/O but slow down detection of code changes |
60-
| `checkForChangeNoMatchInterval` | Seconds between file-change checks for templates that were **not** identified as tasks in the previous check. Typically set higher than `checkForChangeInterval` |
85+
| `templatePathRecursive` | When `true` (default), scan `templatePath` recursively for `.cfm` task templates |
86+
| `checkForChangeInterval` | Seconds between file-change checks for components/templates already identified as tasks. Higher values reduce I/O but slow detection of code changes |
87+
| `checkForChangeNoMatchInterval` | Seconds between checks for files that were **not** tasks on the previous scan. Keep this higher when the folder contains many non-task files |
6188
| `settingLocation` | Cache definition name used to persist task settings (e.g. paused state) across server instances. Leave empty if not needed |
62-
| `checkForChangeSettingInterval` | Seconds between checks for setting changes in the `settingLocation` cache. When server A pauses a task, this is the maximum delay before server B picks up the change |
63-
| `logName` | Name of the Lucee log file the TEG writes to (default: `application`) |
89+
| `checkForChangeSettingInterval` | Seconds between checks for setting changes in `settingLocation`. When server A pauses a task, this is the maximum delay before server B picks up the change (`0` = disabled) |
90+
| `logName` | Lucee log file the TEG writes to (default: `application`) |
6491

65-
The TEG uses four log levels:
92+
Gateway instance settings take precedence over environment variables and JVM system properties.
6693

67-
- **debug** — logged during regular task execution
68-
- **info** — logged when tasks are added, modified, or removed
69-
- **warn** — logged when a task fails to execute
70-
- **error** — logged when the TEG itself encounters an unexpected exception
94+
#### Activator
7195

72-
#### Configuration via Environment Variables or System Properties
96+
The activator is a component whose `active()` method returns whether the gateway should run. Default: `org.lucee.cfml.tasks.Activator` (always active). Override via JVM system property or environment variable only:
97+
98+
| Environment Variable | System Property | Default |
99+
|---|---|---|
100+
| `TASKS_EVENT_GATEWAY_ACTIVATOR` | `tasks.event.gateway.activator` | `org.lucee.cfml.tasks.Activator` |
73101

74-
The gateway instance custom settings can also be supplied via environment variables or JVM system properties. Note that values set in the gateway instance configuration (`.CFConfig.json` or the Lucee Admin) always take precedence.
102+
Use a custom activator for maintenance windows, feature flags, or cluster leadership.
75103

76-
The table below lists each environment variable alongside its system property equivalent:
104+
#### Configuration via environment variables or system properties
77105

78106
| Environment Variable | System Property | Default |
79107
|---|---|---|
80108
| `TASKS_EVENT_GATEWAY_PACKAGE` | `tasks.event.gateway.package` | `org.lucee.cfml.tasks` |
81109
| `TASKS_EVENT_GATEWAY_TEMPLATE_PATH` | `tasks.event.gateway.template.path` | *(empty)* |
82110
| `TASKS_EVENT_GATEWAY_TEMPLATE_PATH_RECURSIVE` | `tasks.event.gateway.template.path.recursive` | `true` |
83111
| `TASKS_EVENT_GATEWAY_CHECKFORCHANGEINTERVAL` | `tasks.event.gateway.checkForChangeInterval` | `10` |
84-
| `TASKS_EVENT_GATEWAY_CHECKFORCHANGEINTERVAL` | `tasks.event.gateway.checkForChangeNoMatchInterval` | `60` |
85-
| `TASKS_EVENT_GATEWAY_SETTINGLOCATION` | `tasks.event.gateway.settinglocation` | *(empty)* |
112+
| `TASKS_EVENT_GATEWAY_CHECKFORCHANGENOMATCHINTERVAL` | `tasks.event.gateway.checkForChangeNoMatchInterval` | `60` |
113+
| `TASKS_EVENT_GATEWAY_SETTINGLOCATION` | `tasks.event.gateway.settingLocation` | *(empty)* |
86114
| `TASKS_EVENT_GATEWAY_CHECKFORCHANGESETTINGINTERVAL` | `tasks.event.gateway.checkForChangeSettingInterval` | `0` |
87115
| `TASKS_EVENT_GATEWAY_LOG` | `tasks.event.gateway.log` | `application` |
88116

89-
### Task Configuration
117+
### Runtime API (`sendGatewayMessage`)
90118

91-
Each task configures itself via `property` declarations:
119+
Send messages to a running gateway instance using `sendGatewayMessage()` (first argument = gateway instance ID):
92120

93121
```cfc
94-
property name="concurrentThreadCount" type="numeric" default=1;
95-
property name="howLongToSleepBeforeTheCall" type="numeric" default=1000;
96-
property name="howLongToSleepAfterTheCall" type="numeric" default=1000;
97-
property name="howLongToSleepAfterTheCallWhenError" type="numeric" default=10000;
98-
property name="howLongToWaitForTaskOnStop" type="numeric" default=10000;
99-
property name="forceStop" type="boolean" default=true;
122+
// current gateway state
123+
state = sendGatewayMessage("my-task", { action: "state" });
124+
125+
// JSON snapshot of tasks, threads, and settings
126+
info = sendGatewayMessage("my-task", { action: "info" });
127+
dump(deserializeJSON(info));
128+
129+
// pause / resume by full task name (component path)
130+
sendGatewayMessage("my-task", { action: "pause", task: "core.tasks.MyWorker" });
131+
sendGatewayMessage("my-task", { action: "resume", task: "core.tasks.MyWorker" });
132+
```
133+
134+
When `settingLocation` is configured, pause state is persisted in cache and survives gateway restarts; with `checkForChangeSettingInterval` > 0, other servers pick up changes within that interval.
135+
136+
### Logging
137+
138+
The TEG uses four log levels:
139+
140+
- **debug** — regular task execution
141+
- **info** — tasks added, modified, or removed
142+
- **warn** — task execution failures, activator fallback
143+
- **error** — unexpected gateway exceptions
144+
145+
Ensure the log named in `logName` exists in `.CFConfig.json` or the Administrator.
146+
147+
### Task configuration
148+
149+
Each task configures itself via `property` declarations on the base component `org.lucee.cfml.Task`:
150+
151+
```cfc
152+
property name="concurrentThreadCount" type="numeric" default=1;
153+
property name="howLongToSleepBeforeTheCall" type="numeric" default=0;
154+
property name="howLongToSleepAfterTheCall" type="numeric" default=0;
155+
property name="howLongToSleepAfterTheCallWhenError" type="numeric" default=60000;
156+
property name="howLongToWaitForTaskOnStop" type="numeric" default=10000;
157+
property name="forceStop" type="boolean" default=false;
100158
```
101159

102-
Full documentation for each property is in `/source/components/org/lucee/cfml/Task.cfc`.
160+
| Property | Description |
161+
|---|---|
162+
| `concurrentThreadCount` | Number of parallel threads for this task |
163+
| `howLongToSleepBeforeTheCall` | Milliseconds to sleep before each `invoke()` |
164+
| `howLongToSleepAfterTheCall` | Milliseconds to sleep after a successful `invoke()` |
165+
| `howLongToSleepAfterTheCallWhenError` | Milliseconds to sleep after an exception (prevents log spam) |
166+
| `howLongToWaitForTaskOnStop` | Grace period when stopping the gateway before optional force-terminate |
167+
| `forceStop` | Terminate the task thread if it does not stop within the grace period |
103168

104-
One setting worth highlighting: `howLongToSleepAfterTheCallWhenError` controls how long a task pauses after an exception. This is critical — you generally want a task to **slow down** after an error, not spin rapidly and flood the logs.
169+
Full property documentation is in `source/components/org/lucee/cfml/Task.cfc`.
105170

106-
### Listener Configuration
171+
### Listener configuration
107172

108-
Each listener configures which tasks it applies to via `allow` and `deny` properties:
173+
Each listener configures which tasks it applies to via `allow` and `deny` properties (comma-separated lists with `*` and `?` wildcards). Deny overrides allow.
109174

110175
```cfc
111176
property name="allow" type="string" default="*";
112177
property name="deny" type="string" default="";
113178
```
114179

115-
Full documentation is in `/source/components/org/lucee/cfml/Listener.cfc`.
180+
Full documentation is in `source/components/org/lucee/cfml/Listener.cfc`.
116181

117182
---
118183

119184
## Creating a Task
120185

121-
The TEG scans the component package defined in the gateway instance configuration (e.g. `core.tasks`, which maps to `/core/tasks`). Any component in that package that extends `org.lucee.cfml.Task` is automatically picked up and executed.
186+
The TEG scans the component package from gateway configuration (e.g. `core.tasks``/core/tasks`). Any component extending `org.lucee.cfml.Task` is picked up and executed automatically. Changes to the CFC are detected on the next scan (see `checkForChangeInterval`).
122187

123188
```cfc
124189
component extends="org.lucee.cfml.Task" {
125190
126-
property name="concurrentThreadCount" type="numeric" default=1;
127-
property name="howLongToSleepBeforeTheCall" type="numeric" default=1000;
128-
property name="howLongToSleepAfterTheCall" type="numeric" default=1000;
129-
property name="howLongToSleepAfterTheCallWhenError" type="numeric" default=10000;
130-
property name="howLongToWaitForTaskOnStop" type="numeric" default=10000;
131-
property name="forceStop" type="boolean" default=true;
132-
133-
public function init() {
134-
systemOutput("----- INIT -----", 1, 1);
135-
}
191+
property name="howLongToSleepAfterTheCall" type="numeric" default=5000;
136192
137193
public void function invoke(
138194
required string id,
@@ -142,52 +198,45 @@ component extends="org.lucee.cfml.Task" {
142198
date lastExecutionDate,
143199
struct lastError
144200
) {
145-
systemOutput("---- #id# it:#iterations# err:#errors# last:#lastExecutionDate?:'<none>'# last-time:#lastExecutionTime?:'<none>'# #now()# -----", 1, 1);
146-
sleep(randRange(1000, 5000));
201+
// id = thread instance id; iterations/errors = lifetime counters for this instance
202+
systemOutput("Worker #id# iteration #iterations# at #now()#", 1, 1);
147203
}
148204
}
149205
```
150206

151-
All properties have defaults inherited from the abstract component and are therefore optional.
207+
All task properties inherit defaults from the abstract component and are optional.
152208

153-
## .cfm-Based Tasks
209+
## .cfm-based tasks
154210

155-
Tasks can also be defined as plain `.cfm` templates. This is useful for reusing existing scheduled tasks without rewriting them. These tasks are executed as internal requests, so `Application.cfc` runs first.
211+
Tasks can also be plain `.cfm` templatesuseful for reusing existing scheduled templates without rewriting them. They run as internal requests, so `Application.cfc` runs first.
156212

157-
Set the `templatePath` in the gateway instance configuration to the folder containing your templates. Each template must include the following metadata comment to be recognised as a task:
213+
Set `templatePath` in the gateway configuration. Each template must include task metadata in a comment block:
158214

159215
```cfm
160216
<!---
161217
@task "CFML Dummy Task"
162-
@description "This CFML Dummy Task is just to show the functionality"
218+
@description "Example template-based task"
163219
@concurrentThreadCount 1
164220
@howLongToSleepBeforeTheCall 2000
165221
@howLongToSleepAfterTheCall 2000
166222
@howLongToSleepAfterTheCallWhenError 10000
167223
@howLongToWaitForTaskOnStop 10000
168-
@forceStop true
224+
@forceStop false
169225
--->
170226
```
171227

172-
These settings follow the same rules as property-based task configuration. Inside the template, the same arguments passed to `invoke()` are available via the `url` scope:
173-
174-
- `url.id`
175-
- `url.iterations`
176-
- `url.errors`
177-
- `url.lastExecutionTime`
178-
- `url.lastExecutionDate`
179-
- `url.lastError`
228+
Inside the template, the same arguments passed to `invoke()` are available in the `url` scope: `url.id`, `url.iterations`, `url.errors`, `url.lastExecutionTime`, `url.lastExecutionDate`, `url.lastError`.
180229

181230
---
182231

183232
## Creating a Listener
184233

185-
Listeners are defined the same way as tasks — they just extend a different base component. Create a component that extends `org.lucee.cfml.Listener` and implement its abstract functions:
234+
Listeners extend `org.lucee.cfml.Listener` and implement `onExecutionStart`, `onExecutionEnd`, and `onError`. They are discovered from the same package as tasks.
186235

187236
```cfc
188237
component extends="org.lucee.cfml.Listener" {
189238
190-
property name="allow" type="string" default="*";
239+
property name="allow" type="string" default="MyWorker,*Queue*";
191240
property name="deny" type="string" default="";
192241
193242
public void function onError(
@@ -201,40 +250,25 @@ component extends="org.lucee.cfml.Listener" {
201250
date lastExecutionDate,
202251
struct lastError
203252
) {
204-
systemOutput("----- MyListener.onError -----", 1, 1);
205-
systemOutput(error.message, 1, 1);
206-
}
207-
208-
public void function onExecutionStart(
209-
component instance,
210-
string task,
211-
required string id,
212-
required numeric iterations,
213-
required numeric errors,
214-
numeric lastExecutionTime,
215-
date lastExecutionDate,
216-
struct lastError
217-
) {
218-
systemOutput("----- MyListener.onExecutionStart -----", 1, 1);
219-
if (!isNull(lastError)) lastError = lastError.message;
220-
systemOutput(arguments, 1, 1);
253+
log text="Task #task# failed: #error.message#" type="error";
221254
}
222255
223-
public void function onExecutionEnd(
224-
component instance,
225-
string task,
226-
required string id,
227-
required numeric iterations,
228-
required numeric errors,
229-
numeric lastExecutionTime,
230-
date lastExecutionDate,
231-
struct lastError
232-
) {
233-
systemOutput("----- MyListener.onExecutionEnd -----", 1, 1);
234-
if (!isNull(lastError)) lastError = lastError.message;
235-
systemOutput(arguments, 1, 1);
236-
}
256+
public void function onExecutionStart(/* same signature as onError minus error */) {}
257+
public void function onExecutionEnd(/* same signature as onError minus error */) {}
237258
}
238259
```
239260

240-
All properties have defaults inherited from the abstract component and are therefore optional.
261+
---
262+
263+
## Troubleshooting
264+
265+
| Symptom | Things to check |
266+
|---|---|
267+
| Gateway state stays `stopped` | Activator `active()` returns false; check gateway log for startup errors |
268+
| Task not picked up | CFC extends `org.lucee.cfml.Task`; package matches `custom.package`; mapping/webroot resolves |
269+
| Template task ignored | `@task` metadata present; file under `templatePath`; path reachable via internal request |
270+
| Changes not applied | Lower `checkForChangeInterval`; confirm file timestamp changed |
271+
| Pause not synced across servers | Same `settingLocation` cache on all nodes; `checkForChangeSettingInterval` > 0 |
272+
| High CPU / log volume after errors | Increase `howLongToSleepAfterTheCallWhenError` on the task |
273+
274+
For more detail, see the [Tasks Event Gateway recipe](https://docs.lucee.org/recipes/task-event-gateway.html) in the Lucee documentation.

0 commit comments

Comments
 (0)