Start learning free

LESSON 115 OF 173 · BACKEND: PAYMENTS AND OTHER SERVICES

When a Job Fails

Practice

The worker from the last lesson marks every job done, whatever the mail service answered. But a busy mail service answers 503 Service Unavailable ("too busy, try later"), and then the email never left. The job says done, so no run will try it again. A job that fails should stay 'waiting', so the next run retries it.

Not forever, though: an email to an address that doesn't exist fails at every run. So each job counts its attempts, in a new column attempts INTEGER NOT NULL DEFAULT 0. DEFAULT 0 fills it in for new jobs, so the INSERT in the pay route stays as it is. After the third failed attempt the job is 'failed': the worker leaves it alone, and a person can look at it.

Counting tries looks the same everywhere. A login that locks after 5 wrong passwords adds 1 first, then compares:

tries := user.WrongPasswords + 1
var state string
if passwordOK {
    state = "open"
} else if tries < 5 {
    state = "try again"
} else {
    state = "locked"
}

Go compiles only when every variable you declare is also read somewhere, and giving it a value doesn't count. So state needs a line that reads it, like the UPDATE that will save your job's status.

The mail service answers 202 when it takes an email. In the tests, POST /check/fail-next makes it answer 503 to the next 4 emails, then it works again:

POST /orders/8/pay                  →  201  {"amount": 80, "order_id": 8, "payment_id": "pay_1"}
POST /check/fail-next {"times": 4}  →  200  {"failing": 4}
POST /admin/run-jobs                →  200  {"ran": 1}
POST /orders/10/pay                 →  201  {"amount": 21, "order_id": 10, "payment_id": "pay_2"}
POST /admin/run-jobs                →  200  {"ran": 2}
GET /check/jobs                     →  200  [{"attempts": 2, "id": 1, "order_id": 8, "status": "waiting"},
                                             {"attempts": 1, "id": 2, "order_id": 10, "status": "waiting"}]
POST /admin/run-jobs                →  200  {"ran": 2}
GET /check/jobs                     →  200  [{"attempts": 3, "id": 1, "order_id": 8, "status": "failed"},
                                             {"attempts": 2, "id": 2, "order_id": 10, "status": "done"}]
GET /check/sent                     →  200  [{"body": "Hello Chloe Ng, …", "id": 1, "subject": "Your order 10 is paid", "to": "chloe@example.com"}]
POST /admin/run-jobs                →  200  {"ran": 0}

Your task: the jobs table's line is gone from your code: write it again with the attempts column. In the worker's loop, keep the answer of sendReceipt in response, add 1 to job.Attempts, declare status and choose it: "done" after a 202, "waiting" while attempts is below 3, "failed" otherwise. Save the status and the attempts with one UPDATE.


Assignment
Follow the Coding Tutorial to count each job's attempts and retry a failed job up to 3 times.


Hint
Check the 202 first, so a job that got through is never sent again. Add 1 to the attempts before you compare with 3: the third failure must already count as 3. Until the UPDATE reads status, Go stops with "declared and not used": that's expected.


STEP 1 OF 11
    Ctrl + Enter to check