{"id":15573869,"url":"https://github.com/gch1p/jobd","last_synced_at":"2026-03-05T16:40:09.295Z","repository":{"id":57281273,"uuid":"342622607","full_name":"gch1p/jobd","owner":"gch1p","description":"simple job queue daemon","archived":false,"fork":false,"pushed_at":"2023-04-12T23:20:51.000Z","size":101,"stargazers_count":9,"open_issues_count":1,"forks_count":0,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-04-11T21:40:16.522Z","etag":null,"topics":["jobd"],"latest_commit_sha":null,"homepage":"","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/gch1p.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2021-02-26T15:46:10.000Z","updated_at":"2023-04-12T23:20:49.000Z","dependencies_parsed_at":"2024-10-02T18:24:45.100Z","dependency_job_id":null,"html_url":"https://github.com/gch1p/jobd","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/gch1p%2Fjobd","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/gch1p%2Fjobd/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/gch1p%2Fjobd/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/gch1p%2Fjobd/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/gch1p","download_url":"https://codeload.github.com/gch1p/jobd/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":250546442,"owners_count":21448334,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["jobd"],"created_at":"2024-10-02T18:14:38.065Z","updated_at":"2026-03-05T16:40:09.256Z","avatar_url":"https://github.com/gch1p.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# jobd\n\n**jobd** is a simple job queue daemon that works with persistent queue storage.\nIt uses a MySQL table as a storage backend (for queue input and output).\n\nCurrently, MySQL is the only supported storage. Other backends may be easily\nsupported though.\n\nIt is by design that jobd never adds nor deletes jobs from storage. It only\nreads (when a certain request arrives) and updates them (during execution, when\njob status changes). Succeeded or failed, your jobs are never lost.\n\njobd consists of 2 parts:\n\n1. **jobd** is a \"worker\" daemon that reads jobs from the database, enqueues and\n   launches them. There may be multiple instances of jobd running on multiple\n   hosts. Each jobd instance may have unlimited number of queues (called \"targets\"),\n   each with its own concurrency limit.\n\n2. **jobd-master** is a \"master\" or \"central\" daemon that simplifies control over\n   many jobd instances. There should be only one instance of jobd-master running.\n   jobd-master is not required for jobd workers to work (they can work without it),\n   but it's very very useful.\n   \nIn addition, there is a command line utility called **jobctl**.\n\nOriginally, jobd was created as a saner alternative to Gearman. It's been used\nin production with a large PHP web application on multiple servers for quite some\ntime already, and proven to be stable and efficient. We were also monitoring the \nmemory usage of all our jobd instances for two months, and can confirm there\nare no leaks.\n\n## Table of Contents\n\n- [How it works](#how-it-works)\n    - [Targets](#targets)\n    - [Creating jobs](#creating-jobs)\n    - [Launching jobs](#launching-jobs)\n    - [Launching background jobs](#launching-background-jobs)\n    - [Launching manual jobs](#launching-manual-jobs)\n    - [Using jobd-master](#using-jobd-master)\n- [Integration example](#integration-example)\n- [Installation](#installation)\n- [Usage](#usage)\n    - [systemd](#systemd)\n    - [supervisor](#supervisor)\n    - [Other notes](#other-notes)\n- [Configuration](#configuration)\n    - [jobd](#jobd-1)\n    - [jobd-master](#jobd-master)\n    - [jobctl](#jobctl)\n- [Clients](#clients)\n    - [PHP](#php)\n- [Protocol](#protocol)\n    - [Request Message](#request-message)\n        - [jobd requests](#jobd-requests)\n            - [poll(targets: string[])](#polltargets-string)\n            - [pause(targets: string[])](#pausetargets-string)\n            - [continue(targets: string[])](#continuetargets-string)\n            - [status()](#status)\n            - [run-manual(ids: int[])](#run-manualids-int)\n            - [add-target(target: string, concurrency: int)](#add-targettarget-string-concurrency-int)\n            - [remove-target(target: string)](#remove-targettarget-string)\n            - [set-target-concurrency(target: string, concurrency: int)](#set-target-concurrencytarget-string-concurrency-int)\n        - [jobd-master requests](#jobd-master-requests)\n            - [register-worker(targets: string[], name: string)](#register-workertargets-string-name-string)\n            - [poke(targets: string[])](#poketargets-string)\n            - [pause(targets: string[])](#pausetargets-string-1)\n            - [continue(targets: string[])](#continuetargets-string-1)\n            - [status(poll_workers=false: bool)](#statuspoll_workersfalse-bool)\n            - [run-manual(jobs: {id: int, target: string}[])](#run-manualjobs-id-int-target-string)\n    - [Response Message](#response-message)\n    - [Ping and Pong Messages](#ping-and-pong-messages)\nq- [TODO](#todo)\n- [License](#license)\n\n\n## How it works\n\n### Targets\n\nEvery jobd instance has its own set of queues, called **targets**. A name of a\ntarget is an arbitrary string, the length of which should be limited by the size\nof `target` field in the MySQL table.\n\nEach target has its own concurrency limit (the maximum number of jobs that may\nbe executed simultaneously). Targets are loaded from the config at startup, and\nalso may be added or removed at runtime, by\n[`add-target(target: string, concurrency: int)`](#add-targettarget-string-concurrency-int)\nand [`remove-target(target: string)`](#remove-targettarget-string) requests.\n\nThe purpose of targets is to logically separate jobs of different kinds by putting\nthem in different queues. For instance, targets can be used to simulate jobs\npriorities:\n```ini\n[targets]\nlow = 5\nnormal = 5\nhigh = 5 \n```\n\nThe config above defines three targets (or three queues), each with a concurrency\nlimit of `5`.\n\nOr, let's imagine a scenario when you have two kinds of jobs: heavy,\nresource-consuming, long-running jobs (like video processing) and light, fast\nand quick jobs (like sending emails). In this case, you could define two targets,\nlike so:\n```ini\n[targets]\nheavy = 3\nquick = 20\n```\n\nThis config would allow running at most 3 heavy and up to 20 quick jobs\nsimultaneously.\n\n\u003e :thought_balloon: In the author's opinion, the approach of having different\n\u003e targets (queues) for different kinds of jobs is better than having a single\n\u003e queue with each job having a \"priority\".\n\u003e \n\u003e Imagine you had a single queue with maximum number of simultaneously running\n\u003e jobs set to, say, 20. What would happen if you'd add a new job, even with the\n\u003e highest priority possible, when there's already 20 slow jobs running? No matter\n\u003e how high the priority of new job is, it would have to wait.\n\u003e\n\u003e By defining different targets, jobd allows you to create dedicated queues for\n\u003e such jobs, making sure there's always a room for high-priority tasks to run as\n\u003e early as possible.\n\n### Creating jobs\n\nEach job is described by one record in the MySQL table. Here is a table scheme\nwith a minimal required set of fields:\n```mysql\nCREATE TABLE `jobs` (\n  `id` int(10) UNSIGNED NOT NULL AUTO_INCREMENT,\n  `target` char(16) NOT NULL,\n  `time_created` int(10) UNSIGNED NOT NULL,\n  `time_started` int(10) UNSIGNED NOT NULL DEFAULT 0,\n  `time_finished` int(10) UNSIGNED NOT NULL DEFAULT 0,\n  `status` enum('waiting','manual','accepted','running','done','ignored') NOT NULL DEFAULT 'waiting',\n  `result` enum('ok','fail') DEFAULT NULL,\n  `return_code` tinyint(3) UNSIGNED DEFAULT NULL,\n  `sig` char(10) DEFAULT NULL,\n  `stdout` mediumtext DEFAULT NULL,\n  `stderr` mediumtext DEFAULT NULL,\n  PRIMARY KEY (`id`),\n  KEY `status_target_idx` (`status`, `target`, `id`)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8;\n```\n\nAs you can see:\n1.  Each job has a unique ID. You don't need to care about assigning IDs because \n    `AUTO_INCREMENT` is used.\n2.  Each job is associated with some target, or, in other words, is put to\n    some queue. More about targets in the [Targets](#targets) section.\n3.  There are `time_created`, `time_started` and `time_finished` fields, and it's \n    not hard to guess what their meaning. When creating a job, you should fill\n    the `time_created` field with a UNIX timestamp. jobd will update the two other\n    fields while executing the job.\n4.  Each job has a `status`.\n    - A job must be created with status set to `waiting` or `manual`.\n    - A status becomes `accepted` when jobd reads the job from the table and\n      puts it to a queue, or it might become `ignored` in case of some error, like\n      invalid `target`, or invalid `status` when processing a\n      [run-manual(ids: int)](#run-manualids-int) request.\n    - Right before a job is getting started, its status becomes `running`.\n    - Finally, when it's done, it is set to `done`.\n5.  The `result` field indicates whether a job completed successfully or not.\n    - It is set to `ok` if the return code of launched command was `0`.\n    - Otherwise, it is set to `fail`.\n6.  The `return_code` field is filled with the actual return code.\n7.  If the job process was killed by a POSIX signal, the signal name is written\n    to the `sig` field.\n8.  stdout and stderr of the process are written to `stdout` and `stderr` fields,\n    accordingly.\n    \n\u003e :warning: In a real world, you'll want to have a few more additional fields,\n\u003e like `job_name` or `job_data`.\u003cbr\u003e\n\u003e Check out the [integration example](#integration-example).\n\nTo create a new job, it must be added to the table. As mentioned earlier, adding\nor removing rows from the table is by design outside the jobd's area of\nresponsibility. A user must add jobs to the table manually.\n\nThere are two kinds of jobs, in terms of how they are executed: **background** and\n**manual** (or foreground).\n\n* Background jobs are created with `waiting` status. When jobd gets new\n  jobs from the table (which happens upon receiving a\n  [`poll(target: strings[])`](#polltargets-string); this process is described in\n  detail in the [launching background jobs](#launching-background-jobs) section),\n  such jobs are added to their queues and get executed at some point, depending\n  on the current queue status and concurrency limit. A user does not have control\n  of the execution flow, the only feedback it has is the fields in the table that\n  are going to be updated before, during and after the execution. At some point,\n  `status` will become `done`, `result` and other fields will have their values\n  filled too, and that's it.\n* Manual, or foreground jobs, is a different story. They must be created with\n  `status` set to `manual`. These jobs are processed only upon a \n  [`run-manual(ids: int[])`](#run-manualids-int) request. When jobd receives such\n  request, it reads and launches the specified jobs, waits for the results and\n  sends them back to the client in a response. Learn more about it under the \n  [launching manual jobs](#launching-manual-jobs) section.\n\n### Launching jobs\n\n**Launching** (or **executing**) a job means **running a command** specified in\nthe config as the `launcher`, replacing the `{id}` template with current job id.\n\nFor example, if you have this in the config:\n```\nlauncher = php /home/user/job-launcher.php {id}\n```\nand jobd is currently executing a job with id 123, it will launch\n`php /home/user/job-launcher.php 123`.\n\n### Launching background jobs\n\nAfter jobs have been added to storage, jobd must be notified about it. This is \ndone by a [`poll(targets: string[])`](#polltargets-string) request that a user \n(a client) sends to the jobd instance. The `targets` argument is an array\n(a list) of `targets` to poll. It can be omitted; in that case jobd will query\nfor jobs for all targets it is serving.\n\nWhen jobd receives a [`poll(targets: string[])`](#polltargets-string) request and\nspecified targets are not full (haven't reached their concurrency limit), it\nperforms a `SELECT` query with `status='waiting'` condition and `LIMIT` set\naccording to the `mysql_fetch_limit`config value.\n\nFor example, after receiving the [`poll(['1/low', '1/normal'])`](#polltargets-string) \nrequest, assuming `mysql_fetch_limit` is set to `100`, jobd will query jobs from\na table roughly like this:\n```mysql\nSELECT id, status, target FROM jobs WHERE status='waiting' AND target IN ('1/low', '1/normal') ORDER BY id LIMIT 0, 100 FOR UPDATE\n```\n\u003e However, if all specified targets are full at the time of jobd receiving the\n\u003e [`poll(targets: string[])`](#polltargets-string) request, the query will be\n\u003e delayed until at least one of the targets becomes available for new jobs.\n\nThen it loops through results, and either accepts a job (by setting\nits status in the table to `accepted`) or ignores it (by setting a status to\n`ignored`). Accepted jobs are then added to internal queues according to their\ntargets and executed.\n\n### Launching manual jobs\n\n\"Manual\" jobs is a way of launching jobs in a blocking way (\"blocking\" from a\nclient's point of view).\n\nAfter jobs have been added to a storage with `status` set to `manual`, a client\nhas to send a [`run-manual(ids: int[])`](#run-manualids-int) request to a jobd\ninstance that serves targets the new jobs are assigned to. When jobd receives \nsuch request, it performs a `SELECT` query with `id IN ({ids})` condition.\n\nFor example, while processing the [`run-manual([5,6,7])`](#run-manualids-int)\nrequest, jobd will make a query that looks roughly something like this:\n```mysql\nSELECT id, status, target FROM jobs WHERE id IN ('5', '6', '7') FOR UPDATE\n```\n\nThen it loops through results, and either accepts a job (by setting its status\nin the table to `accepted`) or ignores it (by setting its status to `ignored`).\nAccepted jobs are then added to internal queues according to their targets and\nexecuted.\n\nWhen all requested jobs are finished, one way or another (succeeded or failed),\njobd compiles and sends a response to the client. The response format is described\n[here](#run-manualids-int).\n\n### Using jobd-master\n\nIf you had only one worker instance (one server, one node), it would not be a\nproblem to use it directly. But what if you have tens or hundreds of servers,\neach of them serving different targets? This is where **jobd-master** comes in \nplay: it's been created to simplify usage and management of multiple workers.\n\nThere should be only one instance of **jobd-master** running. All jobd workers\nare supposed to connect to it at startup. These connections between each worker\nand jobd-master are persistent.\n\nWhen jobd worker connects to the master instance, it sends it the list of targets\nthe worker is serving (see Fig. 1).\n\nLet's imagine we have three servers (`srv-1`, `srv-2` and `srv-3`), each having\na jobd worker. All of them are serving common target named `any`, but they're also\nconfigured to serve their own `low`, `normal` and `high` targets `s/low`,\n`s/normal` and `s/high` respectively (where `s` is the server number):\n\n```\nFigure 1\n\n┌────────────┐ ┌────────────┐  ┌────────────┐\n│ jobd on    │ │ jobd on    │  │ jobd on    │\n│ srv-1      │ │ srv-2      │  │ srv-3      │\n├────────────┤ ├────────────┤  ├────────────┤\n│ Targets:   │ │ Targets:   │  │ Targets:   │\n│ - any      │ │ - any      │  │ - any      │\n│ - 1/low    │ │ - 2/low    │  │ - 3/low    │\n│ - 1/normal │ │ - 2/normal │  │ - 3/normal │\n│ - 1/high   │ │ - 2/high   │  │ - 3/high   │\n└──────┬─────┘ └─────┬──────┘  └────┬───────┘\n       │             │              │\n       │     ┌───────▼───────┐      │\n       └─────►  jobd-master  ◄──────┘\n             └───────────────┘\n```\n\nWhen targets are added or removed at runtime (by [`add-target()`](#add-targettarget-string-concurrency-int)\nor [`remove-target()`](#remove-targettarget-string) request), workers notify the master\ntoo. Thus, jobd-master always know which workers serve which targets.\n\nTo launch jobd via jobd-master, client needs to send a\n[`poke(targets: string[])`](#poketargets-string) request to jobd-master instance, and\njobd-master will send [`poll()`](#polltargets-string) requests to all appropriate\nworkers.\n\nFor example, if you created, say, 5 jobs: \n\n- 3 for the `any` target,\n- 1 for target `2/normal`, and\n- 1 for target `3/low`,\n  \nyou send [`poke('any', '2/normal', '3/low')`](#poketargets-string)\nrequest to jobd-master. As a result, it will send:  \n\n- [`poll('any')`](#polltargets-string) request to a random worker serving the `any` target, \n- [`poll('2/normal')`](#polltargets-string) request to `srv-2`, and \n- [`poll('3/low')`](#polltargets-string) request to `srv-3`.\n\nAlso, you can launch manual (foreground) jobs in parallel on multiple workers\nvia jobd-master and synchronously (in a blocking way) get all results. To do that,\nyou can use the [`run-manual(jobs: {id: int, target: string}[])`](#run-manualjobs-id-int-target-string)\nrequest.\n\nSee the integration example for real code examples.\n\n## Integration example\n\nPHP: [jobd-php-example](https://github.com/gch1p/jobd-php-example)\n\n## Installation\n\nFirst, you need Node.JS 14 or newer. See [here](https://nodejs.org/en/download/package-manager/)\nnow to install it using package manager.\n\nThen install jobd using npm:\n\n```\nnpm i -g jobd \n```\n\n## Usage\n\n### systemd\n\nOne of possible ways of launching jobd and jobd-master daemons is via systemd.\nThis repository contains basic examples of [`jobd.service`](systemd/jobd.service)\nand [`jobd-master.service`](systemd/jobd-master.service) unit files. Note that \njobs will be launched as the same user the jobd worker is running, so you might\nwant to change that.\n\nCopy `.service` file(s) to `/etc/systemd/system`, then do:\n```shell\nsystemctl daemon-reload\nsystemctl enable jobd\nsystemctl start jobd\n# repeat last two steps for jobd-master, if needed\n```\n\n### supervisor\n\nIf you don't like systemd, [supervisor](http://supervisord.org/) might be an\noption. Create a configuration file in `/etc/supervisor/conf.d` with following\ncontent:\n```ini\n[program:jobd]\ncommand=/usr/bin/jobd --config /etc/jobd.conf\nnumprocs=1\ndirectory=/\nstdout_logfile=/var/log/jobd-stdout.log\nautostart=true\nautorestart=true\nuser=nobody\nstopsignal=TERM\n```\n\nThen use `supervisorctl` to start or stop jobd.\n\n### Other notes\n\n:exclamation: Don't forget to filter access to jobd and jobd-master ports using\na firewall. See a note [here](#protocol) for more info.\n\n## Configuration\n\nConfiguration files are written in ini format. All available options for both\ndaemons, as well as a command-line utility, are described below. You can copy\n[`jobd.conf.example`](jobd.conf.example) and [`jobd-master.conf.example`](jobd-master.conf.example)\nand use them as a template instead of writing configs from scratch.\n\n### jobd\n\nDefault config path is `/etc/jobd.conf`. Use the `--config` option to use\na different path.\n\nWithout section:\n\n- `host` *(required, string)* — jobd server hostname \n- `port` *(required, int)* — jobd server port\n- `password` *(string)* — password for requests\n- `always_allow_localhost` *(boolean, default: `false`)* — when set to `1`\n  or `true`, allows accepting requests from clients connecting from localhost\n  without password\n- `name` *(string, default: `os.hostname()`)* — worker name\n- `master_host` *(string)* — master hostname\n- `master_port` *(int)* — master port. If hostname or port is omitted, jobd\n  will not connect to master.\n- `master_reconnect_timeout` *(int, default: `10`)* — if connection to master\n  failed, jobd will be constantly trying to reconnect. This option specifies a\n  delay between connection attempts, in seconds.\n- `log_file` *(string)* — path to a log file\n- `log_level_file` *(string, default: `warn`)* — minimum level of logs that\n  are written to the file.\u003cbr\u003e\n  Allowed values: `trace`, `debug`, `info`, `warn`, `error`\n- `log_level_console` *(string, default: `warn`)* — minimum level of logs\n  that go to stdout.\n- `mysql_host` *(required, string)* — database host\n- `mysql_port` *(required, int)* — database port\n- `mysql_user` *(required, string)* — database user\n- `mysql_password` *(required, string)* — database password\n- `mysql_database` *(required, string)* — database name\n- `mysql_table` *(required, string)* — table name\n- `mysql_fetch_limit` *(int, default: `100`)* — a number of new jobs to fetch\n  in every request\n- `launcher` *(required, string)* — a template of shell command that will be launched\n  for every job. `{id}` will be replaced with job id\n- `launcher.cwd` *(string, default: `process.cwd()`)* — current working directory\n  for spawned launcher processes\n- `launcher.env.{any}` *(string)* — environment variable for spawned launcher\n  processes\n- `max_output_buffer` *(int, default: `1048576`)*\n\nUnder the `[targets]` section, targets are specified. Each target is specified on\na separate line in the following format:\n```ini\n{target_name} = {n}\n```\nwhere:\n- `{target_name}` *(string)* is target name\n- `{n}` *(int)* is maximum count of simultaneously executing jobs for this target\n\n### jobd-master\n\nDefault config path is `/etc/jobd-master.conf`. Use the `--config` option to use\na different path.\n\n- `host` *(required, string)*\n- `port` *(required, int)*\n- `password` *(string)*\n- `always_allow_localhost` *(boolean, default: `false`)*\n- `ping_interval` *(int, default: `30`)* — specifies interval between workers\n  pings.\n- `poke_throttle_interval` *(int, default: `0.5`)*\n- `log_file` *(string)*\n- `log_level_file` *(string, default: `warn`)* \n- `log_level_console` *(string, default: `warn`)*\n\n### jobctl\n\nDefault config path is `~/.jobctl.conf`.\n\n- `master` (boolean) — same as `--master`.\n- `host` *(string)* — same as `--host`.\n- `port` *(int)* — same as `--port`.\n- `password` *(string)*\n- `log_level` *(string, default: `warn`)*\n\n## Clients\n\n### PHP\n\n[php-jobd-client](https://github.com/gch1p/php-jobd-client) (official)\n\n## Protocol\n\nBy default, jobd and jobd-master listen on TCP ports 7080 and 7081 respectively,\nports can be changed in a config.\n\n\u003e :exclamation: jobd has been created with an assumption that it'll be used in\n\u003e more-or-less trusted environments (like LAN or, at least, servers within one\n\u003e data center) so **no encryption nor authentication mechanisms have been\n\u003e implemented.** All traffic between jobd and clients flow **in plain text**.\n\u003e \n\u003e You can protect a jobd instance with a password though, so at least basic\n\u003e password-based authorization is supported.\n\nBoth daemons receive and send Messages. Each message is followed by `EOT` (`0x4`)\nbyte which indicates an end of a message. Clients may send and receive multiple\nmessages over a single connection. Usually, it's the client who must close the\nconnection, when it's not needed anymore. A server, however, may close the\nconnection in some situations (invalid password, server error, etc).\n\nMessages are encoded as JSON arrays with at least one item, representing\nthe message type:\n```\n[TYPE]\n```\n\nIf a message of specific type has some data, it's placed as a second item:\n```\n[TYPE, DATA]\n```\n\nType of `TYPE` is integer. Supported types are:\n\n- `0`: Request\n- `1`: Response\n- `2`: Ping\n- `3`: Pong\n\n\n### Request Message\n\n`DATA` is a JSON object with following keys:\n\n- ##### `no` (required, int)\n  Unique (per connection) request number. Clients can start counting request\n  numbers from one (`1`) or from any other random number. Each subsequent request\n  should increment this number by 1. Note that zero (`0`) is reserved.\n  \n- ##### `type` (required, string)\n  Request type. Supported request types for [jobd](#jobd-requests) and\n  [jobd-master](#jobd-master-requests) are listed below.\n  \n- ##### `data` (object)\n  Request arguments (if needed): an object, whose keys and values represent\n  argument names and values.\n  \n- ##### `password` (string)\n  A password, for password-protected instances. Only needed for first request.\n\nExample (w/o trailing `EOT`):\n```\n[0,{no:1,type:'poll',data:{'targets':['target_1','target_2']}}]\n```\n\nHere is the list of supported requests, using `type(arguments)` notation.\n\n#### jobd requests\n\n* ##### `poll(targets: string[])`\n  Get new tasks for specified `targets` from database. If `targets` argument is\n  no specified, get tasks for all serving targets.\n  \n  Response [data](#data-array--object--string--int) type: **string** ('ok').\n\n* ##### `pause(targets: string[])`\n  Pause execution of tasks of specified targets. If `targets` argument is\n  omitted, pauses all targets.\n\n  Response [data](#data-array--object--string--int) type: **string** ('ok').\n\n* ##### `continue(targets: string[])`\n  Continue execution of tasks of specified targets. If `targets` argument is\n  omitted, continues all targets.\n\n  Response [data](#data-array--object--string--int) type: **string** ('ok').\n\n* ##### `status()`\n  Returns status of internal queues and memory usage.\n  \n  Response [data](#data-array--object--string--int) type: **object** with following keys:\n  - `targets` (object\u003ctarget: string, {paused: bool, concurrency: int, length: int}\u003e)\n  - `jobPromisesCount` (int)\n  - `memoryUsage` (NodeJS.MemoryUsage)\n\n* ##### `add-target(target: string, concurrency: int)`\n  Add target.\n\n  Response [data](#data-array--object--string--int) type: **string** ('ok').\n\n* ##### `remove-target(target: string)`\n  Remove target.\n\n  Response [data](#data-array--object--string--int) type: **string** ('ok').\n\n* ##### `set-target-concurrency(target: string, concurrency: int)`\n  Set concurrency limit of target `target`.\n\n  Response [data](#data-array--object--string--int) type: **string** ('ok').\n\n* ##### `run-manual(ids: int[])`\n  Enqueue and run jobs with specified IDs and `status` set to `manual`, and\n  return results.\n\n  Response [data](#data-array--object--string--int) type: **object** with following keys:\n  - `jobs` (object\u003cint, object\u003e)\n    \n    An object whose keys represent succeeded job IDs and whose values are objects\n    with following keys:\n    - `result` (string)\n    - `code` (int)\n    - `signal` (string|null)\n    - `stdout` (string)\n    - `stderr` (string)\n  - `errors` (object\u003cint, string\u003e)\n    An object whose keys represent failed job IDs and whose values are error\n    messages.\n\n* ##### `send-signal(jobs: object\u003cid: int, signal: int\u003e)`\n  Send signals to jobs which are still executing and return results.\n\n  Response [data](#data-array--object--string--int) type: **object** with job IDs as keys and\n  kill status (boolean where true means that signal is successfully delivered) as values.\n\n#### jobd-master requests\n\n* ##### `register-worker(targets: string[], name: string)`\n  Used by jobd instances to register themselves with master. Clients don't need it.\n\n* ##### `poke(targets: string[])`\n  Send [`poll(targets)`](#polltargets-string) requests to all registered workers that serve specified\n  `targets`.\n\n* ##### `pause(targets: string[])`\n  Send [`pause(targets)`](#pausetargets-string) requests to workers serving\n  specified `targets`. If `targets` argument is omitted, sends [`pause()`](#pausetargets-string)\n  to all workers.\n\n* ##### `continue(targets: string[])`\n  Send [`continue(targets)`](#continuetargets-string) requests to workers serving\n  specified `targets`. If `targets` argument is omitted, sends\n  [`continue()`](#continuetargets-string) to all workers.\n\n* ##### `status(poll_workers=false: bool)`\n  Returns list of registered workers and memory usage. If `poll_workers` argument\n  is true, sends [`status()`](#status) request to all workers and includes their responses.\n\n* ##### `run-manual(jobs: {id: int, target: string}[])`\n  Send [`run-manual()`](#run-manualids-int) requests to registered jobd instances\n  serving specified targets, aggregate and return results.\n\n* ##### `send-signal(jobs: {id: int, signal: int, target: string}[])`\n  Send [`send-signal()`](#send-signal-jobs) requests to registered jobd instances\n  serving specified targets, aggregate and return results.\n\n### Response Message\n\n`DATA` is a JSON object with following keys:\n\n- ##### `no` (required, int)\n  [`no`](#no-required-int) of request this response is related to.\n  \n- ##### `data` (array | object | string | int)\n  Data, if request succeeded.\n  \n- ##### `error` (string)\n  Error message, if request failed.\n\nExample (w/o trailing `EOT`):\n```\n[1,{no:1,data:'ok'}]\n```\n\n### Ping and Pong Messages\n\nNo `DATA`.\n\nExample (w/o trailing `EOT`):\n```\n[2]\n```\n\n## TODO\n\n- graceful shutdown of jobd\n- support signals in jobctl\n\n## License\n\nMIT","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fgch1p%2Fjobd","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fgch1p%2Fjobd","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fgch1p%2Fjobd/lists"}