postjob 0.2.2 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/lib/postjob/cli/db.rb +3 -0
- data/lib/postjob/cli/job.rb +15 -7
- data/lib/postjob/cli/ps.rb +31 -4
- data/lib/postjob/cli/run.rb +4 -2
- data/lib/postjob/migrations/002_statuses.rb +18 -0
- data/lib/postjob/migrations/003_postjobs.sql +41 -0
- data/lib/postjob/migrations/003a_processing.sql +3 -0
- data/lib/postjob/migrations/003b_processing_columns.sql +31 -0
- data/lib/postjob/migrations/004_tokens.sql +9 -0
- data/lib/postjob/migrations/005_helpers.sql +28 -0
- data/lib/postjob/migrations/006_enqueue.sql +44 -0
- data/lib/postjob/migrations/006a_processing.sql +48 -0
- data/lib/postjob/migrations/007_job_results.sql +157 -0
- data/lib/postjob/migrations/008_checkout_runnable.sql +78 -0
- data/lib/postjob/migrations/008a_childjobs.sql +82 -0
- data/lib/postjob/migrations/009_tokens.sql +25 -0
- data/lib/postjob/migrations.rb +30 -77
- data/lib/postjob/queue/encoder.rb +1 -1
- data/lib/postjob/queue/notifications.rb +6 -21
- data/lib/postjob/queue/search.rb +4 -4
- data/lib/postjob/queue.rb +47 -209
- data/lib/postjob/runner.rb +22 -13
- data/lib/postjob.rb +21 -19
- data/spec/postjob/enqueue_spec.rb +26 -14
- data/spec/postjob/job_control/error_status_spec.rb +2 -2
- data/spec/postjob/job_control/manual_spec.rb +4 -6
- data/spec/postjob/job_control/max_attempts_spec.rb +3 -1
- data/spec/postjob/process_job_spec.rb +3 -2
- data/spec/postjob/queue/encoder_spec.rb +4 -0
- data/spec/postjob/run_spec.rb +1 -1
- data/spec/postjob/step_spec.rb +2 -2
- data/spec/postjob/{sub_workflow_spec.rb → workflows/child_workflow_spec.rb} +2 -2
- data/spec/spec_helper.rb +1 -1
- data/spec/support/configure_database.rb +1 -0
- data/spec/support/test_helper.rb +4 -4
- metadata +19 -7
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 586b34b133d67462d64f913cf678ae14ef7954b4
|
4
|
+
data.tar.gz: 4ecb46ab647b4337c56af775110d64cc16a36cb5
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: a0a946eed383baa9e3c23a0bacc0a52175d917f7f976d6dc6f10b1ffe7e87ab8d3da7add93145f426f2e637613c3a0417dfd5a11404e6ac227d57a765e26a08b
|
7
|
+
data.tar.gz: 132c173dc2da7630433511078c8eb7b75766fa00da4d3e52bb88e8a05c84254f1ae27490e7d3dc9ab762af962d6f2e813d42440d1f6e204cd1305e2e16225f48
|
data/lib/postjob/cli/db.rb
CHANGED
data/lib/postjob/cli/job.rb
CHANGED
@@ -1,5 +1,3 @@
|
|
1
|
-
# rubocop:disable Metrics/MethodLength
|
2
|
-
|
3
1
|
module Postjob::CLI
|
4
2
|
# Enqueues a workflow
|
5
3
|
#
|
@@ -37,15 +35,11 @@ module Postjob::CLI
|
|
37
35
|
Simple::SQL.ask <<~SQL, job_ids
|
38
36
|
UPDATE postjob.postjobs
|
39
37
|
SET
|
40
|
-
status='ready', next_run_at=now(),
|
38
|
+
status='ready', next_run_at=(now() at time zone 'utc'),
|
41
39
|
results=null, failed_attempts=0, error=NULL, error_message=NULL, error_backtrace=NULL
|
42
40
|
WHERE id = ANY($1);
|
43
41
|
SQL
|
44
42
|
|
45
|
-
Simple::SQL.ask <<~SQL
|
46
|
-
NOTIFY postjob_notifications
|
47
|
-
SQL
|
48
|
-
|
49
43
|
logger.warn "The following jobs have been reset: #{job_ids.join(', ')}"
|
50
44
|
end
|
51
45
|
|
@@ -66,6 +60,20 @@ module Postjob::CLI
|
|
66
60
|
SQL
|
67
61
|
end
|
68
62
|
|
63
|
+
# runs a job as soon as possible
|
64
|
+
def job_force(job_id)
|
65
|
+
connect_to_database!
|
66
|
+
|
67
|
+
job_id = Integer(job_id)
|
68
|
+
|
69
|
+
Simple::SQL.ask <<~SQL, job_id
|
70
|
+
UPDATE postjob.postjobs
|
71
|
+
SET
|
72
|
+
next_run_at=now() at time zone 'utc'
|
73
|
+
WHERE id = $1 AND status IN ('ready', 'err', 'sleep')
|
74
|
+
SQL
|
75
|
+
end
|
76
|
+
|
69
77
|
private
|
70
78
|
|
71
79
|
# parses "foo:bar,baz:quibble" into { "foo" => "bar", "baz" => "quibble"}
|
data/lib/postjob/cli/ps.rb
CHANGED
@@ -1,3 +1,6 @@
|
|
1
|
+
# rubocop:disable Lint/HandleExceptions
|
2
|
+
# rubocop:disable Metrics/ModuleLength
|
3
|
+
|
1
4
|
module Postjob::CLI
|
2
5
|
private
|
3
6
|
|
@@ -24,7 +27,16 @@ module Postjob::CLI
|
|
24
27
|
error,
|
25
28
|
COALESCE((results->0)::varchar, error_message) AS result,
|
26
29
|
next_run_at,
|
27
|
-
(now() at time zone 'utc')
|
30
|
+
next_run_at - (now() at time zone 'utc') AS next_run_in,
|
31
|
+
to_char(EXTRACT(EPOCH FROM (now() at time zone 'utc') - created_at), '999999999.99') AS age,
|
32
|
+
CASE WHEN processing_started_at IS NOT NULL THEN
|
33
|
+
format(
|
34
|
+
'%s/%s',
|
35
|
+
to_char(EXTRACT(EPOCH FROM (now() at time zone 'utc') - processing_started_at), '999999999.99'),
|
36
|
+
processing_max_duration
|
37
|
+
)
|
38
|
+
END AS processing,
|
39
|
+
COALESCE(processing_client, '') || COALESCE('/' || processing_client_identifier, '') AS worker,
|
28
40
|
tags
|
29
41
|
FROM postjob.postjobs
|
30
42
|
WHERE #{condition_fragment}
|
@@ -56,6 +68,11 @@ module Postjob::CLI
|
|
56
68
|
expect! limit => /\A\d+\z/
|
57
69
|
limit = Integer(limit)
|
58
70
|
|
71
|
+
connect_to_database!
|
72
|
+
|
73
|
+
# check for timed out and zombie processes
|
74
|
+
# ::Postjob::Queue.checkout(nil)
|
75
|
+
|
59
76
|
unless ids.empty?
|
60
77
|
ps_full(*ids, limit: limit, tags: tags)
|
61
78
|
return
|
@@ -70,11 +87,20 @@ module Postjob::CLI
|
|
70
87
|
print_sql limit: limit, query: query
|
71
88
|
end
|
72
89
|
|
90
|
+
def ps_top(*ids, limit: "100", tags: nil)
|
91
|
+
loop do
|
92
|
+
system "clear"
|
93
|
+
ps(*ids, limit: limit, tags: tags)
|
94
|
+
sleep 1
|
95
|
+
end
|
96
|
+
rescue Interrupt
|
97
|
+
end
|
98
|
+
|
73
99
|
# Show all information about this job
|
74
100
|
def ps_show(id, *ids)
|
75
101
|
ids = ([id] + ids).map { |s| Integer(s) }
|
76
102
|
|
77
|
-
jobs = Simple::SQL.
|
103
|
+
jobs = Simple::SQL.records <<~SQL, ids, into: Postjob::Job
|
78
104
|
SELECT * FROM postjob.postjobs WHERE id = ANY($1)
|
79
105
|
SQL
|
80
106
|
|
@@ -84,6 +110,8 @@ module Postjob::CLI
|
|
84
110
|
end
|
85
111
|
|
86
112
|
def ps_full(*ids, limit: 100, tags: nil)
|
113
|
+
connect_to_database!
|
114
|
+
|
87
115
|
conditions = []
|
88
116
|
conditions << tags_condition(tags)
|
89
117
|
conditions << ids_condition(ids)
|
@@ -110,8 +138,7 @@ module Postjob::CLI
|
|
110
138
|
end
|
111
139
|
|
112
140
|
def print_sql(limit:, query:)
|
113
|
-
|
114
|
-
records = Simple::SQL.all("#{query} LIMIT $1+1", limit, into: Hash)
|
141
|
+
records = Simple::SQL.records("#{query} LIMIT $1+1", limit)
|
115
142
|
|
116
143
|
tp records[0, limit]
|
117
144
|
if records.length > limit
|
data/lib/postjob/cli/run.rb
CHANGED
@@ -20,8 +20,10 @@ module Postjob::CLI
|
|
20
20
|
|
21
21
|
connect_to_database!
|
22
22
|
|
23
|
-
|
24
|
-
|
23
|
+
logger.success "Starting runner with pid #{$$}"
|
24
|
+
|
25
|
+
processed = Postjob.run(count: count) do |job_id|
|
26
|
+
logger.info "Processed job w/id #{job_id}" if job_id
|
25
27
|
STDERR.print "." unless quiet
|
26
28
|
end
|
27
29
|
|
@@ -0,0 +1,18 @@
|
|
1
|
+
pg_types = <<~SQL
|
2
|
+
SELECT pg_namespace.nspname AS schema, pg_type.typname AS name
|
3
|
+
FROM pg_type
|
4
|
+
LEFT JOIN pg_namespace on pg_namespace.oid=pg_type.typnamespace
|
5
|
+
SQL
|
6
|
+
|
7
|
+
unless SQL.ask("SELECT 1 FROM (#{pg_types}) sq WHERE (schema,name) = ($1, $2)", SCHEMA_NAME, "statuses")
|
8
|
+
SQL.exec <<~SQL
|
9
|
+
CREATE TYPE #{SCHEMA_NAME}.statuses AS ENUM (
|
10
|
+
'ready', -- process can run
|
11
|
+
'sleep', -- process has external dependencies to wait for.
|
12
|
+
'failed', -- process failed, with nonrecoverable error
|
13
|
+
'err', -- process errored (with recoverable error)
|
14
|
+
'timeout', -- process timed out
|
15
|
+
'ok' -- process succeeded
|
16
|
+
);
|
17
|
+
SQL
|
18
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
CREATE TABLE IF NOT EXISTS {SCHEMA_NAME}.postjobs (
|
2
|
+
-- id values, readonly once created
|
3
|
+
id BIGSERIAL PRIMARY KEY, -- process id
|
4
|
+
parent_id BIGINT REFERENCES {SCHEMA_NAME}.postjobs ON DELETE CASCADE, -- parent process id
|
5
|
+
full_id VARCHAR, -- full process id
|
6
|
+
root_id BIGINT, -- root process id
|
7
|
+
|
8
|
+
created_at timestamp NOT NULL DEFAULT (now() at time zone 'utc'), -- creation timestamp
|
9
|
+
updated_at timestamp NOT NULL DEFAULT (now() at time zone 'utc'), -- update timestamp
|
10
|
+
|
11
|
+
queue VARCHAR, -- queue name. (readonly)
|
12
|
+
workflow VARCHAR NOT NULL, -- e.g. "MyJobModule" (readonly)
|
13
|
+
workflow_method VARCHAR NOT NULL DEFAULT 'run', -- e.g. "run" (readonly)
|
14
|
+
workflow_version VARCHAR NOT NULL DEFAULT '', -- e.g. "1.0"
|
15
|
+
args JSONB, -- args
|
16
|
+
|
17
|
+
-- process state ----------------------------------------------------
|
18
|
+
|
19
|
+
status {SCHEMA_NAME}.statuses DEFAULT 'ready',
|
20
|
+
next_run_at timestamp DEFAULT (now() at time zone 'utc'), -- when possible to run next?
|
21
|
+
timing_out_at timestamp, -- job times out after this timestamp
|
22
|
+
failed_attempts INTEGER NOT NULL DEFAULT 0, -- failed how often?
|
23
|
+
max_attempts INTEGER NOT NULL DEFAULT 1, -- maximum attempts before failing
|
24
|
+
|
25
|
+
-- process result ---------------------------------------------------
|
26
|
+
|
27
|
+
results JSONB,
|
28
|
+
error VARCHAR,
|
29
|
+
error_message VARCHAR,
|
30
|
+
error_backtrace JSONB,
|
31
|
+
|
32
|
+
-- custom fields
|
33
|
+
workflow_status VARCHAR,
|
34
|
+
tags JSONB
|
35
|
+
);
|
36
|
+
|
37
|
+
-- [TODO] check indices
|
38
|
+
CREATE INDEX IF NOT EXISTS postjobs_tags_idx
|
39
|
+
ON {SCHEMA_NAME}.postjobs USING GIN (tags jsonb_path_ops);
|
40
|
+
CREATE INDEX IF NOT EXISTS postjobs_parent_id_idx
|
41
|
+
ON {SCHEMA_NAME}.postjobs(parent_id);
|
@@ -0,0 +1,31 @@
|
|
1
|
+
DO $$
|
2
|
+
BEGIN
|
3
|
+
ALTER TABLE {SCHEMA_NAME}.postjobs ADD COLUMN processing_client varchar;
|
4
|
+
EXCEPTION
|
5
|
+
WHEN duplicate_column THEN RAISE NOTICE 'column {SCHEMA_NAME}.postjobs.processing_client already exists';
|
6
|
+
END;
|
7
|
+
$$;
|
8
|
+
|
9
|
+
DO $$
|
10
|
+
BEGIN
|
11
|
+
ALTER TABLE {SCHEMA_NAME}.postjobs ADD COLUMN processing_client_identifier varchar;
|
12
|
+
EXCEPTION
|
13
|
+
WHEN duplicate_column THEN RAISE NOTICE 'column {SCHEMA_NAME}.postjobs.processing_client_identifier already exists';
|
14
|
+
END;
|
15
|
+
$$;
|
16
|
+
|
17
|
+
DO $$
|
18
|
+
BEGIN
|
19
|
+
ALTER TABLE {SCHEMA_NAME}.postjobs ADD COLUMN processing_started_at timestamp;
|
20
|
+
EXCEPTION
|
21
|
+
WHEN duplicate_column THEN RAISE NOTICE 'column {SCHEMA_NAME}.postjobs.processing_started_at already exists';
|
22
|
+
END;
|
23
|
+
$$;
|
24
|
+
|
25
|
+
DO $$
|
26
|
+
BEGIN
|
27
|
+
ALTER TABLE {SCHEMA_NAME}.postjobs ADD COLUMN processing_max_duration float;
|
28
|
+
EXCEPTION
|
29
|
+
WHEN duplicate_column THEN RAISE NOTICE 'column {SCHEMA_NAME}.postjobs.processing_max_duration already exists';
|
30
|
+
END;
|
31
|
+
$$;
|
@@ -0,0 +1,9 @@
|
|
1
|
+
CREATE TABLE IF NOT EXISTS {SCHEMA_NAME}.tokens (
|
2
|
+
id BIGSERIAL PRIMARY KEY,
|
3
|
+
postjob_id BIGINT REFERENCES {SCHEMA_NAME}.postjobs ON DELETE CASCADE,
|
4
|
+
token UUID NOT NULL,
|
5
|
+
created_at timestamp NOT NULL DEFAULT (now() at time zone 'utc')
|
6
|
+
);
|
7
|
+
|
8
|
+
CREATE INDEX IF NOT EXISTS tokens_postjob_id_idx ON {SCHEMA_NAME}.tokens(postjob_id);
|
9
|
+
CREATE INDEX IF NOT EXISTS tokens_token_idx ON {SCHEMA_NAME}.tokens(token);
|
@@ -0,0 +1,28 @@
|
|
1
|
+
CREATE OR REPLACE FUNCTION {SCHEMA_NAME}._wakeup_runners() RETURNS TRIGGER AS $$
|
2
|
+
BEGIN
|
3
|
+
NOTIFY {CHANNEL};
|
4
|
+
RETURN NEW;
|
5
|
+
END;
|
6
|
+
$$ LANGUAGE plpgsql;
|
7
|
+
|
8
|
+
BEGIN;
|
9
|
+
DROP TRIGGER IF EXISTS _wakeup_runners ON {TABLE_NAME};
|
10
|
+
|
11
|
+
CREATE TRIGGER _wakeup_runners AFTER INSERT OR UPDATE
|
12
|
+
ON {TABLE_NAME}
|
13
|
+
FOR EACH STATEMENT
|
14
|
+
EXECUTE PROCEDURE {SCHEMA_NAME}._wakeup_runners();
|
15
|
+
COMMIT;
|
16
|
+
|
17
|
+
CREATE OR REPLACE FUNCTION {SCHEMA_NAME}._wakeup_parent_job(job_id BIGINT) RETURNS VOID AS $$
|
18
|
+
BEGIN
|
19
|
+
UPDATE postjobs
|
20
|
+
SET
|
21
|
+
status='ready',
|
22
|
+
next_run_at=(now() at time zone 'utc'),
|
23
|
+
updated_at=(now() at time zone 'utc')
|
24
|
+
WHERE
|
25
|
+
status='sleep'
|
26
|
+
AND id=(SELECT parent_id FROM postjobs WHERE id=job_id);
|
27
|
+
END;
|
28
|
+
$$ LANGUAGE plpgsql;
|
@@ -0,0 +1,44 @@
|
|
1
|
+
CREATE OR REPLACE FUNCTION {SCHEMA_NAME}.enqueue(
|
2
|
+
queue VARCHAR,
|
3
|
+
workflow VARCHAR,
|
4
|
+
workflow_method VARCHAR,
|
5
|
+
workflow_version VARCHAR,
|
6
|
+
args JSONB,
|
7
|
+
parent_id BIGINT,
|
8
|
+
tags JSONB,
|
9
|
+
max_attempts INTEGER,
|
10
|
+
timeout DOUBLE PRECISION)
|
11
|
+
RETURNS SETOF {SCHEMA_NAME}.postjobs
|
12
|
+
AS $$
|
13
|
+
DECLARE
|
14
|
+
job_id BIGINT;
|
15
|
+
BEGIN
|
16
|
+
-- set defaults ---------------------------------------------------
|
17
|
+
workflow_version := COALESCE(workflow_version, '');
|
18
|
+
queue := COALESCE(queue, 'q');
|
19
|
+
max_attempts := COALESCE(max_attempts, 5);
|
20
|
+
|
21
|
+
-- create postjobs entry ------------------------------------------
|
22
|
+
INSERT INTO {SCHEMA_NAME}.postjobs (
|
23
|
+
queue, workflow, workflow_method, workflow_version, args,
|
24
|
+
parent_id, tags, max_attempts,
|
25
|
+
timing_out_at
|
26
|
+
)
|
27
|
+
VALUES(
|
28
|
+
queue, workflow, workflow_method, workflow_version, args,
|
29
|
+
parent_id, tags, max_attempts,
|
30
|
+
(now() at time zone 'utc') + timeout * interval '1 second'
|
31
|
+
) RETURNING {SCHEMA_NAME}.postjobs.id INTO job_id;
|
32
|
+
|
33
|
+
-- fill in root_id and full_id ------------------------------------
|
34
|
+
UPDATE postjobs
|
35
|
+
SET
|
36
|
+
root_id=COALESCE((SELECT root_id FROM postjobs s WHERE s.id=postjobs.parent_id), id),
|
37
|
+
full_id=COALESCE((SELECT full_id FROM postjobs s WHERE s.id=postjobs.parent_id) || '.' || id, id::varchar)
|
38
|
+
WHERE id=job_id;
|
39
|
+
|
40
|
+
-- return the job -------------------------------------------------
|
41
|
+
RETURN QUERY
|
42
|
+
SELECT * FROM postjobs WHERE id=job_id;
|
43
|
+
END;
|
44
|
+
$$ LANGUAGE plpgsql;
|
@@ -0,0 +1,48 @@
|
|
1
|
+
CREATE OR REPLACE FUNCTION {SCHEMA_NAME}.set_client_identifier(client_identifier varchar) RETURNS VOID AS $$
|
2
|
+
BEGIN
|
3
|
+
PERFORM set_config('{SCHEMA_NAME}.client_identifier', client_identifier, false);
|
4
|
+
END;
|
5
|
+
$$ LANGUAGE plpgsql;
|
6
|
+
|
7
|
+
--SELECT {SCHEMA_NAME}.set_client_identifier('nope');
|
8
|
+
|
9
|
+
CREATE OR REPLACE FUNCTION {SCHEMA_NAME}._client_identifier() RETURNS varchar AS $$
|
10
|
+
BEGIN
|
11
|
+
RETURN current_setting('{SCHEMA_NAME}.client_identifier');
|
12
|
+
EXCEPTION
|
13
|
+
WHEN OTHERS THEN RETURN NULL;
|
14
|
+
END;
|
15
|
+
$$ LANGUAGE plpgsql;
|
16
|
+
|
17
|
+
CREATE OR REPLACE FUNCTION {SCHEMA_NAME}._set_job_processing(job_id BIGINT) RETURNS VOID AS $$
|
18
|
+
DECLARE
|
19
|
+
v_pid int;
|
20
|
+
BEGIN
|
21
|
+
v_pid := pg_backend_pid();
|
22
|
+
|
23
|
+
UPDATE postjobs
|
24
|
+
SET
|
25
|
+
status='processing',
|
26
|
+
processing_client=(SELECT client_addr || ':' || client_port FROM pg_stat_activity WHERE pid = v_pid),
|
27
|
+
processing_client_identifier={SCHEMA_NAME}._client_identifier(),
|
28
|
+
processing_started_at=now() at time zone 'utc',
|
29
|
+
processing_max_duration=30 * 60, -- default max duration of processing: 30 minutes.
|
30
|
+
error=NULL,
|
31
|
+
error_message=NULL,
|
32
|
+
error_backtrace=NULL,
|
33
|
+
next_run_at=NULL
|
34
|
+
WHERE id=job_id;
|
35
|
+
END;
|
36
|
+
$$ LANGUAGE plpgsql;
|
37
|
+
|
38
|
+
CREATE OR REPLACE FUNCTION {SCHEMA_NAME}._reset_job_processing(job_id BIGINT) RETURNS VOID AS $$
|
39
|
+
BEGIN
|
40
|
+
UPDATE postjobs
|
41
|
+
SET
|
42
|
+
processing_client=NULL,
|
43
|
+
processing_client_identifier=NULL,
|
44
|
+
processing_started_at=NULL,
|
45
|
+
processing_max_duration=NULL
|
46
|
+
WHERE id=job_id;
|
47
|
+
END;
|
48
|
+
$$ LANGUAGE plpgsql;
|
@@ -0,0 +1,157 @@
|
|
1
|
+
CREATE OR REPLACE FUNCTION {SCHEMA_NAME}._update_job_version(job_id BIGINT, p_version VARCHAR) RETURNS VOID AS $$
|
2
|
+
BEGIN
|
3
|
+
IF p_version IS NOT NULL THEN
|
4
|
+
UPDATE postjobs
|
5
|
+
SET
|
6
|
+
workflow_version=p_version,
|
7
|
+
updated_at=(now() at time zone 'utc')
|
8
|
+
WHERE id=job_id;
|
9
|
+
END IF;
|
10
|
+
END;
|
11
|
+
$$ LANGUAGE plpgsql;
|
12
|
+
|
13
|
+
CREATE OR REPLACE FUNCTION {SCHEMA_NAME}.set_job_result(job_id BIGINT, p_results JSONB, p_version VARCHAR) RETURNS VOID AS $$
|
14
|
+
BEGIN
|
15
|
+
PERFORM _update_job_version(job_id, p_version);
|
16
|
+
PERFORM _reset_job_processing(job_id);
|
17
|
+
|
18
|
+
UPDATE postjobs
|
19
|
+
SET
|
20
|
+
results=p_results,
|
21
|
+
error=NULL,
|
22
|
+
error_message=NULL,
|
23
|
+
error_backtrace=NULL,
|
24
|
+
status='ok',
|
25
|
+
next_run_at=NULL
|
26
|
+
WHERE id=job_id;
|
27
|
+
|
28
|
+
PERFORM _wakeup_parent_job(job_id);
|
29
|
+
END;
|
30
|
+
$$ LANGUAGE plpgsql;
|
31
|
+
|
32
|
+
CREATE OR REPLACE FUNCTION {SCHEMA_NAME}.set_job_pending(job_id BIGINT, p_version VARCHAR) RETURNS VOID AS $$
|
33
|
+
BEGIN
|
34
|
+
PERFORM _update_job_version(job_id, p_version);
|
35
|
+
PERFORM _reset_job_processing(job_id);
|
36
|
+
|
37
|
+
UPDATE postjobs
|
38
|
+
SET status='sleep', next_run_at=NULL
|
39
|
+
WHERE id=job_id;
|
40
|
+
END;
|
41
|
+
$$ LANGUAGE plpgsql;
|
42
|
+
|
43
|
+
-- If this is a recoverable error and if we have another run possible
|
44
|
+
-- we'll set next_run_at, and the status to "err", otherwise
|
45
|
+
-- next_run_at will be NULL and the status would be "failed" or "timeout"
|
46
|
+
CREATE OR REPLACE FUNCTION {SCHEMA_NAME}._prepare_next_run(
|
47
|
+
job_id BIGINT,
|
48
|
+
p_status {SCHEMA_NAME}.statuses,
|
49
|
+
p_fast_mode BOOLEAN) RETURNS VOID AS $$
|
50
|
+
DECLARE
|
51
|
+
next_run_at_basetime DOUBLE PRECISION;
|
52
|
+
BEGIN
|
53
|
+
IF p_status NOT IN ('err', 'timeout', 'failed') THEN
|
54
|
+
RAISE 'Invalid status value %', p_status;
|
55
|
+
END IF;
|
56
|
+
|
57
|
+
-- If this is a recoverable error we check if we have any remaining attempts.
|
58
|
+
IF p_status = 'err' THEN
|
59
|
+
IF (SELECT max_attempts - failed_attempts FROM postjobs WHERE id=job_id) > 0 THEN
|
60
|
+
p_status = 'err';
|
61
|
+
ELSE
|
62
|
+
p_status = 'failed';
|
63
|
+
END IF;
|
64
|
+
END IF;
|
65
|
+
|
66
|
+
-- set status, clear next_run_at
|
67
|
+
UPDATE postjobs
|
68
|
+
SET
|
69
|
+
status=p_status,
|
70
|
+
next_run_at=NULL
|
71
|
+
WHERE id=job_id;
|
72
|
+
|
73
|
+
-- fill in next_run_at
|
74
|
+
IF p_status != 'failed' AND p_status != 'timeout' THEN
|
75
|
+
next_run_at_basetime := CASE WHEN p_fast_mode THEN 0.01 ELSE 10 END;
|
76
|
+
|
77
|
+
UPDATE postjobs
|
78
|
+
SET
|
79
|
+
next_run_at=(now() at time zone 'utc') + next_run_at_basetime * pow(1.5, failed_attempts) * interval '1 second'
|
80
|
+
WHERE id=job_id;
|
81
|
+
END IF;
|
82
|
+
END;
|
83
|
+
$$ LANGUAGE plpgsql;
|
84
|
+
|
85
|
+
CREATE OR REPLACE FUNCTION {SCHEMA_NAME}.set_job_error(
|
86
|
+
job_id BIGINT,
|
87
|
+
p_error VARCHAR,
|
88
|
+
p_error_message VARCHAR,
|
89
|
+
p_error_backtrace JSONB,
|
90
|
+
p_status {SCHEMA_NAME}.statuses,
|
91
|
+
p_version VARCHAR,
|
92
|
+
p_fast_mode BOOLEAN) RETURNS VOID AS $$
|
93
|
+
BEGIN
|
94
|
+
PERFORM _update_job_version(job_id, p_version);
|
95
|
+
PERFORM _reset_job_processing(job_id);
|
96
|
+
|
97
|
+
-- write error info
|
98
|
+
UPDATE postjobs
|
99
|
+
SET
|
100
|
+
error=p_error,
|
101
|
+
error_message=p_error_message,
|
102
|
+
error_backtrace=p_error_backtrace,
|
103
|
+
failed_attempts=failed_attempts+1,
|
104
|
+
next_run_at=NULL
|
105
|
+
WHERE id=job_id;
|
106
|
+
|
107
|
+
-- prepare next run, if any
|
108
|
+
PERFORM _prepare_next_run(job_id, p_status, p_fast_mode);
|
109
|
+
PERFORM _wakeup_parent_job(job_id);
|
110
|
+
END;
|
111
|
+
$$ LANGUAGE plpgsql;
|
112
|
+
|
113
|
+
CREATE OR REPLACE FUNCTION {SCHEMA_NAME}._set_job_timeout(
|
114
|
+
job_id BIGINT,
|
115
|
+
p_fast_mode BOOLEAN) RETURNS VOID AS $$
|
116
|
+
BEGIN
|
117
|
+
PERFORM _reset_job_processing(job_id);
|
118
|
+
|
119
|
+
-- write error info
|
120
|
+
UPDATE postjobs
|
121
|
+
SET
|
122
|
+
error='Timeout',
|
123
|
+
error_message='timeout',
|
124
|
+
error_backtrace=NULL,
|
125
|
+
failed_attempts=failed_attempts+1,
|
126
|
+
next_run_at=NULL
|
127
|
+
WHERE id=job_id;
|
128
|
+
|
129
|
+
-- prepare next run, if any
|
130
|
+
PERFORM _prepare_next_run(job_id, 'timeout', p_fast_mode);
|
131
|
+
PERFORM _wakeup_parent_job(job_id);
|
132
|
+
END;
|
133
|
+
$$ LANGUAGE plpgsql;
|
134
|
+
|
135
|
+
CREATE OR REPLACE FUNCTION {SCHEMA_NAME}._set_job_zombie(
|
136
|
+
job_id BIGINT,
|
137
|
+
p_fast_mode BOOLEAN) RETURNS VOID AS $$
|
138
|
+
BEGIN
|
139
|
+
PERFORM _reset_job_processing(job_id);
|
140
|
+
|
141
|
+
RAISE NOTICE 'job % is a zombie', job_id;
|
142
|
+
|
143
|
+
-- write error info
|
144
|
+
UPDATE postjobs
|
145
|
+
SET
|
146
|
+
error='Zombie',
|
147
|
+
error_message='zombie',
|
148
|
+
error_backtrace=NULL,
|
149
|
+
failed_attempts=failed_attempts+1,
|
150
|
+
next_run_at=NULL
|
151
|
+
WHERE id=job_id;
|
152
|
+
|
153
|
+
-- prepare next run, if any
|
154
|
+
PERFORM _prepare_next_run(job_id, 'err', p_fast_mode);
|
155
|
+
PERFORM _wakeup_parent_job(job_id);
|
156
|
+
END;
|
157
|
+
$$ LANGUAGE plpgsql;
|
@@ -0,0 +1,78 @@
|
|
1
|
+
CREATE OR REPLACE FUNCTION {SCHEMA_NAME}.time_to_next_job(workflows_with_versions varchar[])
|
2
|
+
RETURNS float
|
3
|
+
AS $$
|
4
|
+
DECLARE
|
5
|
+
p_processable_at timestamp;
|
6
|
+
BEGIN
|
7
|
+
SELECT MIN(processable_at) INTO p_processable_at FROM (
|
8
|
+
SELECT MIN(processing_started_at + processing_max_duration * interval '1 second') AS processable_at
|
9
|
+
FROM {TABLE_NAME}
|
10
|
+
WHERE status IN ('processing')
|
11
|
+
UNION
|
12
|
+
SELECT MIN(timing_out_at) AS processable_at
|
13
|
+
FROM {TABLE_NAME}
|
14
|
+
WHERE status IN ('ready', 'err', 'sleep')
|
15
|
+
UNION
|
16
|
+
SELECT MIN(next_run_at) AS processable_at
|
17
|
+
FROM {TABLE_NAME}
|
18
|
+
WHERE status IN ('ready', 'err')
|
19
|
+
AND workflow || workflow_version = ANY ($1)
|
20
|
+
) sq;
|
21
|
+
|
22
|
+
RETURN EXTRACT(EPOCH FROM p_processable_at - (now() at time zone 'utc'));
|
23
|
+
END;
|
24
|
+
$$ LANGUAGE plpgsql;
|
25
|
+
|
26
|
+
CREATE OR REPLACE FUNCTION {SCHEMA_NAME}.checkout(workflows_with_versions varchar[], p_fast_mode BOOLEAN)
|
27
|
+
RETURNS SETOF {TABLE_NAME}
|
28
|
+
AS $$
|
29
|
+
DECLARE
|
30
|
+
job {TABLE_NAME};
|
31
|
+
BEGIN
|
32
|
+
LOOP
|
33
|
+
-- try to checkout a job. Each of the conditions here is matching
|
34
|
+
-- one of the CASE .. WHEN clauses below.
|
35
|
+
SELECT INTO job *
|
36
|
+
FROM {TABLE_NAME} s
|
37
|
+
WHERE
|
38
|
+
(
|
39
|
+
s.status IN ('processing')
|
40
|
+
AND (s.processing_started_at + s.processing_max_duration * interval '1 second') <= (now() at time zone 'utc')
|
41
|
+
)
|
42
|
+
OR
|
43
|
+
(
|
44
|
+
s.status IN ('ready', 'err', 'sleep')
|
45
|
+
AND s.timing_out_at <= (now() at time zone 'utc')
|
46
|
+
)
|
47
|
+
OR
|
48
|
+
(
|
49
|
+
s.status IN ('ready', 'err')
|
50
|
+
AND s.next_run_at <= (now() at time zone 'utc')
|
51
|
+
AND s.workflow || s.workflow_version = ANY ($1)
|
52
|
+
)
|
53
|
+
ORDER BY (LEAST(s.next_run_at, s.timing_out_at, (s.processing_started_at + s.processing_max_duration * interval '1 second')))
|
54
|
+
FOR UPDATE SKIP LOCKED
|
55
|
+
LIMIT 1;
|
56
|
+
|
57
|
+
CASE
|
58
|
+
WHEN job.id IS NULL THEN
|
59
|
+
-- couldn't find a job?
|
60
|
+
EXIT;
|
61
|
+
WHEN job.status='processing' AND
|
62
|
+
(job.processing_started_at + job.processing_max_duration * interval '1 second') <= (now() at time zone 'utc') THEN
|
63
|
+
-- a zombie: the worker probably died.
|
64
|
+
PERFORM {SCHEMA_NAME}._set_job_zombie(job.id, p_fast_mode);
|
65
|
+
CONTINUE;
|
66
|
+
WHEN job.status IN ('ready', 'err', 'sleep') AND job.timing_out_at <= (now() at time zone 'utc') THEN
|
67
|
+
-- job timed out? mark it as such, and try next one.
|
68
|
+
PERFORM {SCHEMA_NAME}._set_job_timeout(job.id, p_fast_mode);
|
69
|
+
CONTINUE;
|
70
|
+
ELSE
|
71
|
+
-- set job to processing
|
72
|
+
PERFORM _set_job_processing(job.id);
|
73
|
+
RETURN QUERY SELECT * FROM {TABLE_NAME} WHERE id=job.id;
|
74
|
+
EXIT;
|
75
|
+
END CASE;
|
76
|
+
END LOOP;
|
77
|
+
END;
|
78
|
+
$$ LANGUAGE plpgsql;
|