WEB / WEB-19.03
Scheduled jobs and async processing
Separate a job becoming due, admission to start and its durable result. Examine why an admitted Atlas public-note job can still fail.
1. Core · Mechanism
Read every scenario without motion
Admit + finish
- The job first has a due time
For slot-10, due=10 is selected. There is no admission, worker start or result yet. These times use a small logical clock rather than wall time.
- Logical scheduler
- due=10 / slot-10
- Local dispatcher
- No admission yet
- Application worker
- Worker has not started
- SQLite result owner
- resultRows=0 / processedRows=0
- The scheduler observed time
The scheduler’s observed value is separate from the job’s due time. Their difference is evaluated under the example’s start-lateness policy.
- Logical scheduler
- observed=12 / lateness=2
- Local dispatcher
- No admission yet
- Application worker
- Worker has not started
- SQLite result owner
- resultRows=0 / processedRows=0
- Admission permitted the start
Lateness is within the selected start window, so the dispatcher admits the job. Admission does not mean the worker finished or the result committed.
- Logical scheduler
- observed=12 / lateness=2
- Local dispatcher
- admitted=true / job pending
- Application worker
- Worker has not started
- SQLite result owner
- resultRows=0 / processedRows=0
- The worker started the job
The worker starts the known public-note job. Job data carries purpose and note ID; a browser request object is not treated as transferred into this work.
- Logical scheduler
- observed=12 / lateness=2
- Local dispatcher
- admitted=true / job pending
- Application worker
- Public-note job running
- SQLite result owner
- resultRows=0 / processedRows=0
- Result and marker share a transaction
Result and processed marker are prepared in one transaction. An independent reader cannot see these pending rows before COMMIT; starting a transaction does not establish a durable result.
- Logical scheduler
- observed=12 / lateness=2
- Local dispatcher
- admitted=true / job pending
- Application worker
- Public-note job running
- SQLite result owner
- Result/marker pending / reader sees 0
- The durable outcome is known separately
In the normal branch COMMIT completes and the independent reader sees result and marker. The worker’s finished record and earlier admission describe different completions.
- Logical scheduler
- observed=12 / lateness=2
- Local dispatcher
- admitted=true / job pending
- Application worker
- finished=13 / COMMIT complete
- SQLite result owner
- resultRows=1 / processedRows=1 committed
Late skip
- The job first has a due time
For slot-10, due=10 is selected. There is no admission, worker start or result yet. These times use a small logical clock rather than wall time.
- Logical scheduler
- due=10 / slot-10
- Local dispatcher
- No admission yet
- Application worker
- Worker has not started
- SQLite result owner
- resultRows=0 / processedRows=0
- The scheduler observed time
The scheduler’s observed value is separate from the job’s due time. Their difference is evaluated under the example’s start-lateness policy.
- Logical scheduler
- observed=14 / lateness=4
- Local dispatcher
- No admission yet
- Application worker
- Worker has not started
- SQLite result owner
- resultRows=0 / processedRows=0
- The start window was missed
Lateness of 4 exceeds maxStartAge=3. The dispatcher does not admit the job; being due does not remove that condition. The worker has not started.
- Logical scheduler
- observed=14 / lateness=4
- Local dispatcher
- skip-late / admitted=false
- Application worker
- Worker has not started
- SQLite result owner
- resultRows=0 / processedRows=0
- The skipped job has no result
The independent result reader sees resultRows=0. Here there is no result because the job was not admitted, unlike failure after admission.
- Logical scheduler
- observed=14 / lateness=4
- Local dispatcher
- skip-late / admitted=false
- Application worker
- Worker has not started
- SQLite result owner
- Independent reader: resultRows=0
Duplicate slot
- The job first has a due time
For slot-10, due=10 is selected. There is no admission, worker start or result yet. These times use a small logical clock rather than wall time.
- Logical scheduler
- due=10 / slot-10
- Local dispatcher
- No admission yet
- Application worker
- Worker has not started
- SQLite result owner
- resultRows=0 / processedRows=0
- The scheduler observed time
The scheduler’s observed value is separate from the job’s due time. Their difference is evaluated under the example’s start-lateness policy.
- Logical scheduler
- observed=12 / lateness=2
- Local dispatcher
- No admission yet
- Application worker
- Worker has not started
- SQLite result owner
- resultRows=0 / processedRows=0
- Admission permitted the start
Lateness is within the selected start window, so the dispatcher admits the job. Admission does not mean the worker finished or the result committed.
- Logical scheduler
- observed=12 / lateness=2
- Local dispatcher
- admitted=true / job pending
- Application worker
- Worker has not started
- SQLite result owner
- resultRows=0 / processedRows=0
- The worker started the job
The worker starts the known public-note job. Job data carries purpose and note ID; a browser request object is not treated as transferred into this work.
- Logical scheduler
- observed=12 / lateness=2
- Local dispatcher
- admitted=true / job pending
- Application worker
- Public-note job running
- SQLite result owner
- resultRows=0 / processedRows=0
- Result and marker share a transaction
Result and processed marker are prepared in one transaction. An independent reader cannot see these pending rows before COMMIT; starting a transaction does not establish a durable result.
- Logical scheduler
- observed=12 / lateness=2
- Local dispatcher
- admitted=true / job pending
- Application worker
- Public-note job running
- SQLite result owner
- Result/marker pending / reader sees 0
- The durable outcome is known separately
In the normal branch COMMIT completes and the independent reader sees result and marker. The worker’s finished record and earlier admission describe different completions.
- Logical scheduler
- observed=12 / lateness=2
- Local dispatcher
- admitted=true / job pending
- Application worker
- finished=13 / COMMIT complete
- SQLite result owner
- resultRows=1 / processedRows=1 committed
- A second event for the same slot
The second event carries the same public-note-index/slot-10 identity. The first operation has a committed result, so the effect decision uses its existing marker.
- Logical scheduler
- observed=12 / lateness=2
- Local dispatcher
- Second event for the same slot admitted
- Application worker
- finished=13 / COMMIT complete
- SQLite result owner
- resultRows=1 / processedRows=1 committed
- The existing result is preserved
The worker finds the existing processed marker and inserts no second result. Effects remain 1 despite two events; this observation is scoped to the same job in this example.
- Logical scheduler
- observed=12 / lateness=2
- Local dispatcher
- Second event for the same slot admitted
- Application worker
- finished=13 / COMMIT complete
- SQLite result owner
- Existing marker / effects=1 preserved
Accepted failed
- The job first has a due time
For slot-10, due=10 is selected. There is no admission, worker start or result yet. These times use a small logical clock rather than wall time.
- Logical scheduler
- due=10 / slot-10
- Local dispatcher
- No admission yet
- Application worker
- Worker has not started
- SQLite result owner
- resultRows=0 / processedRows=0
- The scheduler observed time
The scheduler’s observed value is separate from the job’s due time. Their difference is evaluated under the example’s start-lateness policy.
- Logical scheduler
- observed=12 / lateness=2
- Local dispatcher
- No admission yet
- Application worker
- Worker has not started
- SQLite result owner
- resultRows=0 / processedRows=0
- Admission permitted the start
Lateness is within the selected start window, so the dispatcher admits the job. Admission does not mean the worker finished or the result committed.
- Logical scheduler
- observed=12 / lateness=2
- Local dispatcher
- admitted=true / job pending
- Application worker
- Worker has not started
- SQLite result owner
- resultRows=0 / processedRows=0
- The worker started the job
The worker starts the known public-note job. Job data carries purpose and note ID; a browser request object is not treated as transferred into this work.
- Logical scheduler
- observed=12 / lateness=2
- Local dispatcher
- admitted=true / job pending
- Application worker
- Public-note job running
- SQLite result owner
- resultRows=0 / processedRows=0
- The transaction rolled back
A harmless NOT NULL example fails within the transaction. Explicit ROLLBACK leaves resultRows=0 and processedRows=0 without erasing the earlier admitted=true decision.
- Logical scheduler
- observed=12 / lateness=2
- Local dispatcher
- admitted=true / job pending
- Application worker
- Public-note job running
- SQLite result owner
- ROLLBACK / resultRows=0 / processedRows=0
- The admitted job failed
The worker’s final state is failed with no durable result. Earlier admission success does not make the final business result successful; the fields are read separately.
- Logical scheduler
- observed=12 / lateness=2
- Local dispatcher
- admitted=true / job pending
- Application worker
- state=failed / admitted=true remains
- SQLite result owner
- resultRows=0 / processedRows=0
ACTORS · STATE · DATA FLOW
A due time is a starting condition, not a result
A scheduled job expresses intent to start work for a schedule slot. Due time, scheduler observation, worker start, and committed business completion are separate fields. Our public-note indexing fixture uses due=10, observed=12, and finished=13 in logical units, not real cron or wall-clock measurements. Due does not mean a result exists, and started does not mean finished. The application must define units and lateness policy.
Node setTimeout schedules a callback after a delay without exact firing-time or ordering guarantees. A process timer is not a durable scheduling service after process death; lifecycle choices such as unref can permit exit before callback invocation. Kubernetes CronJob uses a different controller model and approximate Job creation: conditions can produce two Jobs or none. Do not transfer guarantees between these products; neither is executed here.
Timezone and missed, late, or overlapping-run policies affect execution identity. Kubernetes exposes controller fields for timezone and concurrency policy; this lesson creates no manifest or cloud resource. Our adapter uses maxStartAge=3 logical units for slot-10: observed=14 skips it while observed=12 admits it. That is a teaching value, not a production default. Duplicate triggers retain the slot-10 job identity; a selected business marker suppresses a second effect without universal scheduler exactly-once behavior.
Async acceptance does not finish out-of-request work
Async processing lets a caller hand off work and receive admission before its final result. AWS Lambda asynchronous invocation queues an event and returns success without the function result as one selected service example; this site executes no Lambda. The local ledger keeps accepted, pending, running, and succeeded/failed separate. Admission success can coexist with later work failure. HTTP 202, if chosen, also means acceptance rather than a final result; no HTTP runtime or browser receipt is measured here.
Application logic outside a request needs its own input, purpose, and data-owner contract. The worker invokes a known service using public note ID and schedule-slot job ID, without a browser request object, user secret, or ambient identity. Result and processed marker share one selected SQLite transaction. A constraint failure explicitly rolls back without a result, while prior admission stays true. Retry and duplicate policy use the job-identity scope; dependency failure and result notification have other boundaries.
2. Red · Trust & Impact
Read every scenario without motion
Late start
- Read the start rule first
For slot-10, due=10 and maxStartAge=3 are selected. These belong to this teaching example, not a measured Node timer or cloud scheduler deadline.
- Admission decision
- due=10 / maxStartAge=3
- Business result
- Result not inspected yet
- Caller expectation
- Logical clock policy
- The job was observed at 14
The difference between observed=14 and due=10 is 4. The job is due, but it is outside the selected start window.
- Admission decision
- observed=14 / lateness=4
- Business result
- Result not inspected yet
- Caller expectation
- Due does not imply admitted
- Admission was rejected
Here admitted=false and outcome=skip-late. The job lacks admission to execute; becoming due does not change that.
- Admission decision
- admitted=false / skip-late
- Business result
- Result not inspected yet
- Caller expectation
- Execution not admitted
- Check the result owner separately
The late branch has resultRows=0. This independently records no durable result for the skipped job; it is not success inferred from a scheduler decision.
- Admission decision
- admitted=false / skip-late
- Business result
- resultRows=0
- Caller expectation
- No durable result in this branch
- The boundary example differs
At observed=13, lateness is 3 and the selected policy admits the job. Observed=14 is rejected. This comparison does not make the policy universal across schedulers.
- Admission decision
- 13 admitted / 14 rejected
- Business result
- late resultRows=0
- Caller expectation
- Selected policy boundary only
- The caller reports this branch only
This job missed the selected window. Report skip-late and the absent result separately rather than presenting an unadmitted job as completed.
- Admission decision
- admitted=false
- Business result
- resultRows=0
- Caller expectation
- Skipped; no successful result
Admit, then fail
- The start was admitted
The failure branch has admitted=true. This permits the job to begin but does not yet establish a durable database result.
- Admission decision
- admitted=true
- Business result
- Result not inspected yet
- Caller expectation
- Outcome pending
- The worker started
Started=12 is the worker’s start value on this example’s logical clock. A start record cannot substitute for finish or commit evidence.
- Admission decision
- admitted=true / started=12
- Business result
- Result not inspected yet
- Caller expectation
- Started != completed
- The transaction failed
A harmless NOT NULL fixture failed and an explicit rollback followed. State=failed; this does not claim a failure was induced in a real cloud worker.
- Admission decision
- admitted=true / started=12
- Business result
- state=failed / rollback
- Caller expectation
- Admission remains distinct
- No durable marker or result remains
ResultRows=0 and processedRows=0 are read together. Admission did not make this transaction leave a completed business effect.
- Admission decision
- admitted=true
- Business result
- resultRows=0 / processedRows=0
- Caller expectation
- No committed effect for this job
- Do not promote admission to final success
If the caller treats admitted=true as final success, it loses the failure record. This branch describes a job that was admitted and then failed.
- Admission decision
- admitted=true
- Business result
- state=failed / resultRows=0
- Caller expectation
- Admitted, then failed
- Require the same job’s result for success
The normal example has finished=13, resultRows=1 and processedRows=1. These cannot be copied into the failure branch; success needs the same operation’s own outcome.
- Admission decision
- failure admitted=true
- Business result
- failure resultRows=0
- Caller expectation
- Different branch != this job evidence
ASSUMPTION · BOUNDARY · CONTROL
Timer counts do not prove business completion
The controlled ledger separates due, observed, admitted, started, and resultRows. A timer tick or scheduler log entry may prove only a trigger boundary; committed data needs another owner observation. due=10 / observed=14 admits no work under the selected late policy. Accepted-but-failed instead has admission=true and resultRows=0. Labeling both simply as no work hides failure ownership.
Drawing a process timer as durable scheduling or a cloud controller as an exact deadline is a scope error. Event-loop load and service policy can affect delay behavior. This lesson makes no delayed-timer latency claim and uses finite logical events. Review follows which owner knows each time/state field without probing a real scheduler or third-party workload.
Duplicate events need a chosen effect contract
Two triggers for one slot can occur under a product policy; even with the same job ID they are separate events. The worker commits its marker with the result, so the second event creates no second effect. Reusing that marker for a different purpose or slot is not the same operation identity. The native fixture handles an ordinary duplicate event without timing attacks or concurrent request floods.
Work continuing after request scope ends depends on runtime behavior too. Shutdown, worker failure, or platform cancellation can stop it. The artifact keeps the selected failure visible in its result store and never promotes admission to success. Review asks where retries restart and how result notification differs from commit, without turning those conditions into exploitation.
3. Blue · Secure development & defense
Read every scenario without motion
Same slot
- Establish operation identity
Here the logical scheduler and dispatcher produce due/observed/admitted; an independent SQLite reader produces resultRows and processedRows. Records correlated by the same slot and jobId are not cloud-scheduler logs. Public-note-index/slot-10 identifies the same job here. Establish the operation before interpreting scheduler event counts.
- Job observation
- jobId=public-note-index/slot-10
- Result record
- Result not inspected yet
- Evidence boundary
- Correlation pending
- Two events identify the same job
The duplicate branch has events=2 for the same jobId. Two events do not establish two distinct operations or completed effects.
- Job observation
- events=2 / same jobId
- Result record
- Result not inspected yet
- Evidence boundary
- Event count != effect count
- Read effects from the independent outcome
Effects=1 and resultRows=1 indicate one durable outcome for this operation. Event and result records answer different questions.
- Job observation
- events=2 / same jobId
- Result record
- effects=1 / resultRows=1
- Evidence boundary
- One outcome observed
- Keep the example’s scope
This branch shows two events for the same slot leaving one effect. It does not establish exactly-once guarantees for every external scheduler or external side effect.
- Job observation
- events=2
- Result record
- effects=1 / resultRows=1
- Evidence boundary
- Scope: authored operation only
- Clock accuracy needs a separate measurement
ExternalSchedulerExecuted=false and nodeTimerTimingMeasured=false. These records do not measure real timer delay or cloud job creation timing.
- Job observation
- externalSchedulerExecuted=false
- Result record
- effects=1
- Evidence boundary
- nodeTimerTimingMeasured=false
- Report only the verified outcome
The verified result is two events and one effect for the authored job. Production scheduler behavior and physical clock guarantees require other observations.
- Job observation
- events=2 / same jobId
- Result record
- effects=1 / resultRows=1
- Evidence boundary
- No real scheduler measurement
Missing outcome
- An admission record exists
Here the logical scheduler and dispatcher produce due/observed/admitted; an independent SQLite reader produces resultRows and processedRows. Records correlated by the same slot and jobId are not cloud-scheduler logs. An audit may contain only admitted=true. Without final state and reportedSuccess, it does not establish the outcome.
- Job observation
- admitted=true
- Result record
- Final state not observed
- Evidence boundary
- Outcome unknown
- Identify fields required by the selector
The lesson’s XPath check looks for admitted=true, state=failed and reportedSuccess=true together. The first field alone cannot evaluate that inconsistency.
- Job observation
- admitted=true
- Result record
- state / reportedSuccess missing
- Evidence boundary
- Required observation incomplete
- Why is zero matches inconclusive?
Missing fields may prevent a selector match. Zero matches then does not prove correct outcome reporting; required observation is absent.
- Job observation
- admission observed
- Result record
- final outcome unavailable
- Evidence boundary
- Insufficient evidence
- The complete failure fixture differs
The complete authored fixture contains admitted=true, state=failed and reportedSuccess=false. Its expected count of 0 comes from the absence of a success claim, not from missing fields.
- Job observation
- authored fixture admitted=true
- Result record
- state=failed / reportedSuccess=false
- Evidence boundary
- expected=0 / complete fields
- The positive fixture checks the detector
The positive fixture selects reportedSuccess=true and expects 1. Finding this example does not establish that real product logs use these fields.
- Job observation
- positive admitted=true
- Result record
- positive state=failed / reportedSuccess=true
- Evidence boundary
- positiveExpected=1
- Preserve unknown for missing observation
The original incomplete record’s final outcome remains unknown. Require final state and reportedSuccess for the same operation rather than borrowing fields from complete or positive fixtures.
- Job observation
- original admission observed
- Result record
- original final outcome unknown
- Evidence boundary
- Missing fields require new evidence
IMPLEMENTATION · SECURITY CONTROL · VERIFICATION
Keep a lifecycle ledger through to results
Here the logical scheduler and dispatcher produce due/observed/admitted; an independent SQLite reader produces resultRows and processedRows. Records correlated by the same slot and jobId are not cloud-scheduler logs. Define the slot, original due, observed start, admitted status, and result owner. For maxStartAge=3 logical units, the native fixture admits observed=13 and skips observed=14; this is no Kubernetes default. A completed request may report only admission. Later status reads or notifications obtain meaning from the final-result owner; here that is only a SQLite observer read.
The worker validates known index-public-note purpose and noteId=tea. Its out-of-request data access follows the job contract rather than browser ambient state. An independent connection cannot see the uncommitted running result, but sees it after COMMIT. This scope proves no crash recovery, cloud service, or user authentication: it covers only own public data.
Misses, failures, and retries are separate branches
The selected late skip creates no job row; an admitted failure keeps a failed lifecycle and rolls back its result/marker transaction. A duplicate admitted event finds the marker and preserves resultRows=1. Branches show different public outcomes. Compensation, retry budgets, and missed-schedule catch-up are service policy, not universal product behavior or production tuning values in this fixture.
Correlate admission, final state and committed result using the same job identity instead of one counter. If final state or reportedSuccess is absent, zero XPath matches do not establish correct outcome reporting: required observation is missing. Complete and positive fixtures explain the detector’s required fields but cannot supply missing fields for another record. Every motion in this lesson represents a local decision step, not a network packet sent to a scheduler or cloud worker.
NOW APPLY IT TO THE CASE
Which records separate a job’s due time, admission and committed result?
Atlas intends to index a public note for slot-10. This example uses a small logical clock rather than wall time: due=10 and maximum start lateness 3. Duru examines admission first, then the same job’s durable outcome.
Read accepted, delivery and committed-effect records at their respective owners. Do not rename unknown caller outcome as business rollback.
Open the related case file ↗READ THE EXAMPLE TOGETHER
Public note schedule/admission/result ledger
Select a field and examine what it supports and where the evidence ends.
Read the raw artifact and field explanations
{
"schedule": {
"purpose": "index-public-note",
"slot": "slot-10",
"due": 10,
"maxStartAge": 3,
"unit": "authored logical clock; not wall clock"
},
"normal": {
"observed": 12,
"lateness": 2,
"admitted": true,
"started": 12,
"finished": 13,
"resultRows": 1,
"processedRows": 1,
"callerAdmissionIsResult": false
},
"late": {
"observed": 14,
"lateness": 4,
"admitted": false,
"outcome": "skip-late",
"resultRows": 0
},
"duplicate": {
"events": 2,
"jobId": "public-note-index/slot-10",
"effects": 1,
"resultRows": 1
},
"failure": {
"admitted": true,
"started": 12,
"state": "failed",
"resultRows": 0,
"processedRows": 0,
"reason": "benign NOT NULL failure; explicit rollback"
},
"boundaries": {
"dueBoundaryObserved13Admitted": true,
"observed14Admitted": false,
"externalSchedulerExecuted": false,
"nodeTimerTimingMeasured": false,
"requestContextCarried": false
},
"selector": {
"language": "XPath",
"xml": "<audit><async admitted=\"true\" state=\"failed\" reportedSuccess=\"false\"/></audit>",
"xpath": "count(/audit/async[@admitted='true' and @state='failed' and @reportedSuccess='true'])",
"expected": 0,
"positiveFixtureXml": "<audit><async admitted=\"true\" state=\"failed\" reportedSuccess=\"true\"/></audit>",
"positiveExpected": 1,
"scope": "Only authored protocol-decision observations, not product log field names or a live HTTP capture.",
"missingFieldBehavior": "Missing observation is insufficient evidence; zero matches is not proof of correct runtime behavior."
}
}- schedule
- Selected logical clock slot and lateness policy.
- normal/late/failure
- Admission and final results are distinct outcomes.
- duplicate
- Selected effect count for one operation identity.
- selector
- Synthetic accepted-but-failed false-success check.
Finite logical adapter and own SQLite only; no actual Node timer, cloud cron/Lambda, background-request runtime, or deadline measurement.
Two reality checks
Due time or async acceptance proves completion.
Trigger/admission is a separate boundary; final outcomes need worker and data-owner evidence.
Every slot runs exactly once at the exact time.
Runtime/product policy can allow late, missed, or duplicate work; the logical fixture is no real timing guarantee.
YOUR TURN
Work through this story.
Predict the outcome, find the evidence boundary, and explain it in your own words.
The job has due=10 and maxStartAge=3. It is observed at observed=14. May it start?
- No. lateness=4 gives skip-late.
- Yes. Every overdue scheduled job must run.
Read the explained answer
No. lateness=4 gives skip-late. — The allowed three-unit window has been exceeded. observed=13 is admitted at the boundary; observed=14 is outside the window.
admitted=true and started=12, but state=failed and resultRows=0. May the caller report success?
Selected lesson artifact fields; teaching example
- failure
- {"admitted":true,"started":12,"state":"failed","resultRows":0,"processedRows":0,"reason":"benign NOT NULL failure; explicit rollback"}
- Yes; admission alone reports a completed job.
- No; the business result failed and rollback occurred.
Read the explained answer
No; the business result failed and rollback occurred. — Admission indicates accepted intent; NOT NULL failure and explicit rollback leave result/processed rows 0.
Explain events 2/effects 1 for the same slot, the logical clock and requestContextCarried=false together.
Read the explained answer
The stable slot jobId is a selected idempotency key; a repeated trigger does not reapply the effect. The clock is authored logical time; Node-timer latency or external-scheduler exactly-once is not measured. Job execution is outside an HTTP request; required trusted subject/scope must come from current execution context, not a correlation ID.
□ I separated trigger count from effect count.
□ I did not count logical timing/correlation as actual scheduler identity.
Open the application console · request / response / browser
Also open request, URL, cookie and DOM examples
Sources and scope
- Node.js v24.13.0 — TimerssetTimeout(callback[, delay[, ...args]]) · timeout.unref() · Cancelling timers
- Kubernetes — CronJobTime zones · Concurrency policy · CronJob limitations: Job creation
- AWS Lambda — Asynchronous invocationInvoking a Lambda function asynchronously
- SQLite — Transaction2. Transactions · 2.1. Read transactions versus write transactions · 2.2. DEFERRED, IMMEDIATE, and EXCLUSIVE transactions · 2.3. Implicit versus explicit transactions
- SQLite — IsolationIsolation Between Database Connections · Isolation And Concurrency
- Python 3.14 — sqlite3Tutorial · sqlite3.connect · Cursor.execute · How to use placeholders to bind values in SQL queries · Transaction control via the isolation_level attribute · How to use the connection context manager