A job queue service¶
Source · io.example.Queue (its own interface) · Unprivileged
queued is a complete Varlink service in about 200 lines. It serves its own
interface, io.example.Queue, and shows every part of the server:
- a typed call (
Depth); - a mutating call that checks the caller's UID (
Enqueue); - a live stream (
Watch); - a typed error (
QueueFull); - validation against the IDL;
- socket activation with a fallback to binding the path itself;
- clean shutdown on
SIGTERM.
Run it¶
Then talk to it with systemd's varlinkctl, or with sdvarlink's client:
Output (captured from systemd's varlinkctl v255)
$ varlinkctl info /tmp/queued.sock
Vendor: sdvarlink examples
Product: queued
Version: 1.0.0
URL: https://github.com/rossedman/sdvarlink/tree/main/examples/queued
Interfaces: io.example.Queue
org.varlink.service
$ varlinkctl call /tmp/queued.sock io.example.Queue.Enqueue '{"command":"make test","priority":"high"}'
{"job":{"id":1,"command":"make test","priority":"high"}}
$ varlinkctl call /tmp/queued.sock io.example.Queue.Enqueue '{"command":"x","priority":"urgent"}'
Method call io.example.Queue.Enqueue() failed: org.varlink.service.InvalidParameter
{"parameter":"priority"}
$ varlinkctl call /tmp/queued.sock io.example.Queue.Watch '{}'
Method call io.example.Queue.Watch() failed: org.varlink.service.ExpectedMore
{}
$ varlinkctl call --more /tmp/queued.sock io.example.Queue.Watch '{"replay":true}'
{"job":{"id":1,"command":"make test","priority":"high"}}
…one line per job as it is enqueued, until Ctrl-C
The interface¶
examples/queued/io.example.Queue.varlink
# A tiny job queue, served by examples/queued.
interface io.example.Queue
type Job(
# Unique job identifier.
id: int,
# What to run.
command: string,
# Scheduling priority.
priority: Priority
)
type Priority(
low,
normal,
high
)
# Returns the number of queued jobs.
method Depth() -> (
jobs: int
)
# Adds a job. Only the queue's owner (and root) may enqueue.
method Enqueue(
command: string,
priority: ?Priority
) -> (
job: Job
)
# Streams queue events as jobs are added.
# [Requires 'more' flag]
method Watch(
# Also replay jobs already queued.
replay: ?bool
) -> (
job: Job
)
# The queue holds as many jobs as it may.
error QueueFull(
capacity: int
)
The code¶
| examples/queued/main.go | |
|---|---|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 | |
How it works¶
- The IDL is embedded with
//go:embedand registered withsrv.Interface, sovarlinkctl introspectshows exactly the file above. - Validation is free. The server rejects the unknown enum value
urgent, a missingcommand, or an extra field before the handler runs. It answersInvalidParameterand names the field, the same way sd-varlink does. Enqueueauthorizes on the peer's UID viacall.Peer(), and returnsvarlink.ErrPermissionDeniedto everyone except root and the user running the service.QueueFullErroris a typed error. It has aVarlinkError()method, so the client receivesio.example.Queue.QueueFullwith{"capacity": N}.Watchis a live stream. Eachsendgoes out immediately, and the handler returns when itsctxends: when the client disconnects or when the server shuts down.- Socket activation first. Under a
.socketunit,ListenActivatedadopts systemd's socket; from a terminal,Listenbinds-socketitself.