postjob 0.2.2 → 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- 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;
|