Skip to content
Syno Package Land
Language: English

Background jobs

The jobs capability adds a persistent job queue and a background worker. A signed-in user picks a file; the app reads it once with the user’s session and keeps a private copy in the package’s data folder. The worker processes the copy after the user signs out or closes the browser. The shipped worker computes the SHA-256 digest of the file. Results stay inside the package: a job never writes to the user’s folders. For that, see Delegate.

Jobs run as the package account. They need no administrator setup. The app stores no cookie, session ID, token or password for a job. Every user has a separate set of jobs.

New project:

Terminal window
synopkgland init -t native-go --with jobs my-jobs

Existing project:

Terminal window
synopkgland add jobs

Both set this in dsm-package.yaml:

capabilities:
jobs: true

jobs: true is the same as jobs: { worker: sha256-v1 }. No other worker name is accepted.

CombinationResult
jobs with daemonCAPABILITY_COMBINATION_INVALID, unless a delegate with lifetime: until-revoked is declared
jobs with a binary backendCAPABILITY_UNSUPPORTED
jobs on armv7Built; not yet verified on a NAS
delegateTurns jobs on automatically

The generated Vue UI adds a Background jobs page. A plain UI has no jobs page, but the routes exist.

The background page lets the user pick a file, submit it, watch the state, cancel, and open the result. With your own code, call these methods through ctx.call. All need a signed-in user.

MethodParamsResult
bg.status{}Limits, queue counts and whether the worker runs
bg.browse{ folder?, offset }A page of folders and files the user can see. Works without the files capability
bg.jobs.submit{ key, folder, relative }{ job, duplicate }
bg.jobs.list{}{ jobs }, newest first
bg.jobs.get{ id }{ job }
bg.jobs.cancel{ id }{ job }
bg.jobs.result{ id }{ job, result }, or not_ready when the job has no result
const key = crypto.randomUUID()
const { job } = await ctx.call<{ job: { id: string; state: string } }>('bg.jobs.submit', {
key,
folder: '/home',
relative: 'report.txt',
})
const { result } = await ctx.call<{ result: { sha256: string; bytes: number } }>('bg.jobs.result', { id: job.id })
  • key identifies the request: 1 to 64 letters, digits, dots, underscores and hyphens. Sending the same key again returns the same job with duplicate: true, so a retry is safe. Keys are separate per owner.
  • folder is a File Station folder path and relative is a file below it.
  • The result is JSON, not a file: { jobId, worker, workerRevision, algorithm: "sha256", sha256, bytes, inputDigest, name }.
  • A job moves through queued, running and ends as succeeded, failed, canceled or interrupted. A job that was running when the worker stopped is interrupted and is not replayed.
  • Closing the browser or signing out does not cancel a job. Only bg.jobs.cancel does.

After an import, the owner can repeat the private copy on a schedule. The app pins the bytes and the digest. It never reads the NAS file again, so a later change to the source file has no effect.

MethodParamsWho
bg.schedules.list{}Owners see their own, administrators see all
bg.schedules.create{ key, jobId, intervalSeconds, consent: true }Owner of the source job
bg.schedules.disable, bg.schedules.delete{ id }Owner or administrator
bg.schedules.enable{ id, consent: true }Administrators only
  • intervalSeconds is exactly 3600 (hourly), 86400 (daily) or 604800 (weekly).
  • consent must be true. Without it the call fails with bad_request.
  • The source job (jobId) must be your own and must have succeeded. Another user’s job returns not_found. A job that is not finished, a scheduled run, or a job whose copy is gone returns input-unavailable. Import the file again.
  • The pinned copy counts toward your 1 MiB and the package’s 16 MiB limits.
  • A tick that is more than 5 seconds late is recorded as missed. Missed runs are never made up. A schedule is not an exact-time or exactly-once guarantee.
  • Scheduled runs leave at least 4 queue slots free for manual jobs.

An owner is a DSM user name together with the numeric UID that DSM resolves for it.

  • Ordinary users list, read, cancel and get results for their own jobs only. A job ID that does not exist and a job that belongs to someone else both return not_found.
  • Administrators see and cancel all jobs. bg.jobs.purge ({ owner: { user, uid } }) removes one owner’s jobs and is administrators only.
  • If DSM cannot resolve the user’s UID, the call fails with identity_unavailable. For administrators this affects only submit and schedule create.
  • An account that is deleted and recreated with the same name but a different UID cannot reach the earlier jobs. If both the name and the UID are reused, jobs are not told apart.
  • Changing permissions on the source file later does not recall copies or results that already exist.
ItemLimit
Jobs running at the same time1
Queued jobs16
Input file512 KiB
Input file on DSM 7.1DSM 7.1 sends the file without Content-Disposition. A file of at most 4 KiB whose whole content is a DSM reply (a JSON object with a success field) is refused as that reply
Total private copies and results16 MiB
Time for one job60 seconds (timeout)
Pending jobs per owner (administrators included)2
Private data per owner (administrators included)1 MiB. Each pending or kept result also counts 512 bytes
Finished jobs kept64
Schedules per package8
Schedule intervals3600, 86400 or 604800 seconds

Two full-size 512 KiB inputs are more than one owner may hold. The data lives in the package’s app data folder. It stays when the package stops and on a normal uninstall. See Data and removal.

CodeCauseFix
session_requiredNo signed-in user, or the DSM session needed to read the file is missingSign in again. With standalone login the call needs the DSM id cookie
dsm_session_requiredStandalone login is on and submit or browse needs the DSM id cookieSign in to DSM on the same host name
limit-exceededThe file is over 512 KiBChoose a smaller file
queue-full16 jobs are queuedWait and retry
quota-exceededThe package’s private storage (16 MiB) is fullWait for jobs to finish, or ask an administrator to purge
owner-queue-fullYou already have 2 pending jobsWait or cancel one
owner-quota-exceededYou hold 1 MiB of private dataWait for results to be purged
schedule-fullThe package has 8 schedulesDelete one
input-unavailableThe kept copy for a schedule is goneImport the file again
conflictThe same key was used for a different input, job or interval, or you cancelled a finished jobUse a new key; check the state
not_readyThe job has no result yetWait for succeeded
not_foundNo such job, or it belongs to another userRefresh the list
identity_unavailableDSM could not resolve the UID of the userRetry; check the DSM account
jobs_busyThe job store was locked for more than 5 secondsRetry
admin_requiredAn administrators-only method such as bg.jobs.purgeUse an administrator account

A failed job has an errorCode:

errorCodeCause
timeoutThe job ran longer than 60 seconds
input-changedThe private copy does not match its stored digest
worker-errorThe worker failed
worker-stoppedThe worker stopped while the job ran. State is interrupted
worker-changedThe job was queued under a different build of the worker. State is interrupted
admission-interruptedA hand-off to a delegate was in progress when the worker restarted. State is interrupted

Codes are described in Errors.