Continous integration / Tests (push) Canceled after 0s
Testing clients / Build beanstalkd (push) Canceled after 0s
Continous integration / Docker build (push) Canceled after 0s
Testing clients / Test Pheanstalk PHP (push) Canceled after 0s
Testing clients / Test Greenstalk Python (3.9) (push) Canceled after 0s
Testing clients / Test Pystalk Python (3.10) (push) Canceled after 0s
498 lines
14 KiB
C
498 lines
14 KiB
C
#include <stdint.h>
|
|
#include <stdlib.h>
|
|
|
|
typedef unsigned char uchar;
|
|
typedef uchar byte;
|
|
typedef unsigned int uint;
|
|
typedef int32_t int32;
|
|
typedef uint32_t uint32;
|
|
typedef int64_t int64;
|
|
typedef uint64_t uint64;
|
|
|
|
typedef struct Ms Ms;
|
|
typedef struct Job Job;
|
|
typedef struct Tube Tube;
|
|
typedef struct Conn Conn;
|
|
typedef struct Heap Heap;
|
|
typedef struct Jobrec Jobrec;
|
|
typedef struct File File;
|
|
typedef struct Socket Socket;
|
|
typedef struct Server Server;
|
|
typedef struct Wal Wal;
|
|
|
|
typedef void(*Handle)(void*, int rw);
|
|
typedef int(FAlloc)(int, int);
|
|
|
|
|
|
// NUM_PRIMES is used in the jobs hashing.
|
|
#if _LP64
|
|
#define NUM_PRIMES 48
|
|
#else
|
|
#define NUM_PRIMES 19
|
|
#endif
|
|
|
|
/* Some compilers (e.g. gcc on SmartOS) define NULL as 0.
|
|
* This is allowed by the C standard, but is unhelpful when
|
|
* using NULL in most pointer contexts with errors turned on. */
|
|
#if (defined(sun) || defined(__sun)) && (defined(__SVR4) || defined(__svr4__))
|
|
#ifdef NULL
|
|
#undef NULL
|
|
#endif
|
|
#define NULL ((void*)0)
|
|
#endif
|
|
|
|
// The name of a tube cannot be longer than MAX_TUBE_NAME_LEN-1
|
|
#define MAX_TUBE_NAME_LEN 201
|
|
|
|
// A command can be at most LINE_BUF_SIZE chars, including "\r\n". This value
|
|
// MUST be enough to hold the longest possible command ("pause-tube a{200} 4294967295\r\n")
|
|
// or reply line ("USING a{200}\r\n").
|
|
#define LINE_BUF_SIZE (11 + MAX_TUBE_NAME_LEN + 12)
|
|
|
|
#define min(a,b) ((a)<(b)?(a):(b))
|
|
|
|
// Jobs with priority less than URGENT_THRESHOLD are counted as urgent.
|
|
#define URGENT_THRESHOLD 1024
|
|
|
|
// The default maximum job size.
|
|
#define JOB_DATA_SIZE_LIMIT_DEFAULT ((1 << 16) - 1)
|
|
|
|
// The maximum value that job_data_size_limit can be set to via "-z".
|
|
// It could be up to INT32_MAX-2 (~2GB), but set it to 1024^3 (1GB).
|
|
// The width is restricted by Jobrec.body_size that is int32.
|
|
#define JOB_DATA_SIZE_LIMIT_MAX 1073741824
|
|
|
|
// The default value for the fsync (-f) parameter, milliseconds.
|
|
#define DEFAULT_FSYNC_MS 50
|
|
|
|
// Use this macro to designate unused parameters in functions.
|
|
#define UNUSED_PARAMETER(x) (void)(x)
|
|
|
|
// version is defined in vers.c, see vers.sh for details.
|
|
extern const char version[];
|
|
|
|
// verbose holds the count of -V parameters; it's a verbosity level.
|
|
extern int verbose;
|
|
|
|
extern struct Server srv;
|
|
|
|
// Replaced by tests to simulate failures.
|
|
extern FAlloc *falloc;
|
|
|
|
// stats structure holds counters for operations, both globally and per tube.
|
|
struct stats {
|
|
uint64 urgent_ct;
|
|
uint64 waiting_ct;
|
|
uint64 buried_ct;
|
|
uint64 reserved_ct;
|
|
uint64 pause_ct;
|
|
uint64 total_delete_ct;
|
|
uint64 total_jobs_ct;
|
|
};
|
|
|
|
|
|
// less_fn is used by the binary heap to determine the order of elements.
|
|
typedef int(*less_fn)(void*, void*);
|
|
|
|
// setpos_fn is used by the binary heap to record the new positions of elements
|
|
// whenever they get moved or inserted.
|
|
typedef void(*setpos_fn)(void*, size_t);
|
|
|
|
struct Heap {
|
|
size_t cap; // capacity of the heap
|
|
size_t len; // amount of elements in the heap
|
|
void **data; // actual elements
|
|
|
|
less_fn less;
|
|
setpos_fn setpos;
|
|
};
|
|
int heapinsert(Heap *h, void *x);
|
|
void* heapremove(Heap *h, size_t k);
|
|
|
|
|
|
struct Socket {
|
|
// Descriptor for the socket.
|
|
int fd;
|
|
|
|
// f can point to srvaccept or prothandle.
|
|
Handle f;
|
|
|
|
// x is passed as first parameter to f.
|
|
void *x;
|
|
|
|
// added value is platform dependend: on OSX it can be > 1.
|
|
// Value of 1 - socket was already added to event notifications,
|
|
// otherwise it is 0.
|
|
int added;
|
|
};
|
|
|
|
int sockinit(void);
|
|
|
|
// sockwant updates event filter for the socket s. rw designates
|
|
// the kind of event we should be notified about:
|
|
// 'r' - read
|
|
// 'w' - write
|
|
// 'h' - hangup (closed connection)
|
|
// 0 - ignore this socket
|
|
int sockwant(Socket *s, int rw);
|
|
|
|
// socknext waits for the next event at most timeout nanoseconds.
|
|
// If event happens before timeout then s points to the corresponding socket,
|
|
// and the kind of event is returned. In case of timeout, 0 is returned.
|
|
int socknext(Socket **s, int64 timeout);
|
|
|
|
|
|
// ms_event_fn is called with the element being inserted/removed and its position.
|
|
typedef void(*ms_event_fn)(Ms *a, void *item, size_t i);
|
|
|
|
// Resizable multiset
|
|
struct Ms {
|
|
size_t len; // amount of stored elements
|
|
size_t cap; // capacity
|
|
size_t last; // position of last taken element
|
|
void **items;
|
|
|
|
ms_event_fn oninsert; // called on insertion of an element
|
|
ms_event_fn onremove; // called on removal of an element
|
|
};
|
|
|
|
void ms_init(Ms *a, ms_event_fn oninsert, ms_event_fn onremove);
|
|
void ms_clear(Ms *a);
|
|
int ms_append(Ms *a, void *item);
|
|
int ms_remove(Ms *a, void *item);
|
|
int ms_contains(Ms *a, void *item);
|
|
void *ms_take(Ms *a);
|
|
|
|
|
|
enum // Jobrec.state
|
|
{
|
|
Invalid,
|
|
Ready,
|
|
Reserved,
|
|
Buried,
|
|
Delayed,
|
|
Copy
|
|
};
|
|
|
|
enum
|
|
{
|
|
Walver = 7
|
|
};
|
|
|
|
// If you modify Jobrec struct, you must increment Walver above.
|
|
//
|
|
// This workflow is expected:
|
|
// 1. If any change needs to be made to the format, first increment Walver.
|
|
// 2. If and only if this is the first such change since the last release:
|
|
// a. Copy-paste relevant file-reading functions in file.c and
|
|
// add the old version number to their names. For example,
|
|
// if you are incrementing Walver from 7 to 8, copy readrec to readrec7.
|
|
// (Currently, there is only one such function, readrec. But if
|
|
// a future readrec calls other version-specific functions,
|
|
// those will have to be copied too.)
|
|
// 3. Add a switch case to fileread for the old version.
|
|
// 4. Modify the current reading function (readrec) to reflect your change.
|
|
//
|
|
// Incrementing Walver for every change, even if not every version
|
|
// will be released, is helpful even if it "wastes" version numbers.
|
|
// It is a really easy thing to do and it means during development
|
|
// you won't have to worry about misinterpreting the contents of a binlog
|
|
// that you generated with a dev copy of beanstalkd.
|
|
|
|
struct Jobrec {
|
|
uint64 id;
|
|
uint32 pri;
|
|
int64 delay;
|
|
int64 ttr;
|
|
int32 body_size;
|
|
int64 created_at;
|
|
|
|
// deadline_at is a timestamp, in nsec, that points to:
|
|
// * time when job will become ready for delayed job,
|
|
// * time when TTR is about to expire for reserved job,
|
|
// * undefined otherwise.
|
|
int64 deadline_at;
|
|
|
|
uint32 reserve_ct;
|
|
uint32 timeout_ct;
|
|
uint32 release_ct;
|
|
uint32 bury_ct;
|
|
uint32 kick_ct;
|
|
byte state;
|
|
};
|
|
|
|
struct Job {
|
|
// persistent fields; these get written to the wal
|
|
Jobrec r;
|
|
|
|
// bookeeping fields; these are in-memory only
|
|
char pad[6];
|
|
Tube *tube;
|
|
Job *prev, *next; // linked list of jobs
|
|
Job *ht_next; // Next job in a hash table list
|
|
size_t heap_index; // where is this job in its current heap
|
|
File *file;
|
|
Job *fnext;
|
|
Job *fprev;
|
|
void *reserver;
|
|
int walresv;
|
|
int walused;
|
|
|
|
char *body; // written separately to the wal
|
|
};
|
|
|
|
struct Tube {
|
|
uint refs;
|
|
char name[MAX_TUBE_NAME_LEN];
|
|
Heap ready;
|
|
Heap delay;
|
|
Ms waiting_conns; // conns waiting for the job at this moment
|
|
struct stats stat;
|
|
uint using_ct;
|
|
uint watching_ct;
|
|
|
|
// pause is set to the duration of the current pause, otherwise 0, in nsec.
|
|
int64 pause;
|
|
|
|
// unpause_at is a timestamp when to unpause the tube, in nsec.
|
|
int64 unpause_at;
|
|
|
|
Job buried; // linked list header
|
|
};
|
|
|
|
|
|
// Prints warning message on stderr in the format:
|
|
// <progname>: FILE:LINE in FUNC: <fmt>: <errno_msg>
|
|
#define twarn(...) __twarn(__VA_ARGS__, "")
|
|
|
|
// Hack to quiet the compiler. When VA_ARGS in twarn() has one element,
|
|
// e.g. twarn("OOM"), its replaced with __twarn("OOM", ""),
|
|
// thus VA_ARGS is expanded to at least one element in warn().
|
|
#define __twarn(fmt, ...) \
|
|
warn("%s:%d in %s: " fmt "%s", __FILE__, __LINE__, __func__, __VA_ARGS__)
|
|
|
|
// Prints warning message on stderr in the format:
|
|
// <progname>: FILE:LINE in FUNC: <fmt>
|
|
#define twarnx(...) __twarnx(__VA_ARGS__, "")
|
|
|
|
// See __twarn macro.
|
|
#define __twarnx(fmt, ...) \
|
|
warnx("%s:%d in %s: " fmt "%s", __FILE__, __LINE__, __func__, __VA_ARGS__)
|
|
|
|
void warn(const char *fmt, ...) __attribute__((format(printf, 1, 2)));
|
|
void warnx(const char *fmt, ...) __attribute__((format(printf, 1, 2)));
|
|
char* fmtalloc(char *fmt, ...) __attribute__((format(printf, 1, 2)));
|
|
void* zalloc(int n);
|
|
#define new(T) zalloc(sizeof(T))
|
|
void optparse(Server*, char**);
|
|
|
|
extern const char *progname;
|
|
|
|
int64 nanoseconds(void);
|
|
int rawfalloc(int fd, int len);
|
|
|
|
// Take ID for a jobs from next_id and allocate and store the job.
|
|
#define make_job(pri,delay,ttr,body_size,tube) \
|
|
make_job_with_id(pri,delay,ttr,body_size,tube,0)
|
|
|
|
Job *allocate_job(int body_size);
|
|
Job *make_job_with_id(uint pri, int64 delay, int64 ttr,
|
|
int body_size, Tube *tube, uint64 id);
|
|
void job_free(Job *j);
|
|
|
|
/* Lookup a job by job ID */
|
|
Job *job_find(uint64 job_id);
|
|
|
|
/* the void* parameters are really job pointers */
|
|
void job_setpos(void *j, size_t pos);
|
|
int job_pri_less(void *ja, void *jb);
|
|
int job_delay_less(void *ja, void *jb);
|
|
|
|
Job *job_copy(Job *j);
|
|
|
|
const char * job_state(Job *j);
|
|
|
|
void job_list_reset(Job *head);
|
|
int job_list_is_empty(Job *head);
|
|
Job *job_list_remove(Job *j);
|
|
void job_list_insert(Job *head, Job *j);
|
|
|
|
/* for unit tests */
|
|
size_t get_all_jobs_used(void);
|
|
|
|
|
|
extern struct Ms tubes;
|
|
|
|
Tube *make_tube(const char *name);
|
|
void tube_dref(Tube *t);
|
|
void tube_iref(Tube *t);
|
|
Tube *tube_find(Ms *tubeset, const char *name);
|
|
Tube *tube_find_or_make(const char *name);
|
|
#define TUBE_ASSIGN(a,b) (tube_dref(a), (a) = (b), tube_iref(a))
|
|
|
|
|
|
Conn *make_conn(int fd, char start_state, Tube *use, Tube *watch);
|
|
|
|
int count_cur_conns(void);
|
|
uint count_tot_conns(void);
|
|
int count_cur_producers(void);
|
|
int count_cur_workers(void);
|
|
|
|
|
|
extern size_t primes[];
|
|
|
|
|
|
extern size_t job_data_size_limit;
|
|
|
|
void prot_init(void);
|
|
int64 prottick(Server *s);
|
|
|
|
void remove_waiting_conn(Conn *c);
|
|
|
|
void enqueue_reserved_jobs(Conn *c);
|
|
|
|
void enter_drain_mode(int sig);
|
|
void h_accept(const int fd, const short which, Server *s);
|
|
int prot_replay(Server *s, Job *list);
|
|
|
|
|
|
int make_server_socket(char *host, char *port);
|
|
|
|
|
|
// CONN_TYPE_* are bit masks used to track the type of connection.
|
|
// A put command adds the PRODUCER type, "reserve*" adds the WORKER type.
|
|
// If connection awaits for data, then it has WAITING type.
|
|
#define CONN_TYPE_PRODUCER 1
|
|
#define CONN_TYPE_WORKER 2
|
|
#define CONN_TYPE_WAITING 4
|
|
|
|
struct Conn {
|
|
Server *srv;
|
|
Socket sock;
|
|
char state; // see the STATE_* description
|
|
char type; // combination of CONN_TYPE_* values
|
|
Conn *next; // only used in epollq functions
|
|
Tube *use; // tube currently in use
|
|
int64 tickat; // time at which to do more work; determines pos in heap
|
|
size_t tickpos; // position in srv->conns, stale when in_conns=0
|
|
byte in_conns; // 1 if the conn is in srv->conns heap, 0 otherwise
|
|
Job *soonest_job;// memoization of the soonest job
|
|
int rw; // currently want: 'r', 'w', or 'h'
|
|
|
|
// How long client should "wait" for the next job; -1 means forever.
|
|
int pending_timeout;
|
|
|
|
// Used to inform state machine that client no longer waits for the data.
|
|
char halfclosed;
|
|
|
|
char cmd[LINE_BUF_SIZE]; // this string is NOT NUL-terminated
|
|
size_t cmd_len;
|
|
int cmd_read;
|
|
|
|
char *reply;
|
|
int reply_len;
|
|
int reply_sent;
|
|
char reply_buf[LINE_BUF_SIZE]; // this string IS NUL-terminated
|
|
|
|
// How many bytes of in_job->body have been read so far. If in_job is NULL
|
|
// while in_job_read is nonzero, we are in bit bucket mode and
|
|
// in_job_read's meaning is inverted -- then it counts the bytes that
|
|
// remain to be thrown away.
|
|
int64 in_job_read;
|
|
Job *in_job; // a job to be read from the client
|
|
|
|
Job *out_job; // a job to be sent to the client
|
|
int out_job_sent; // how many bytes of *out_job were sent already
|
|
|
|
Ms watch; // the set of watched tubes by the connection
|
|
Job reserved_jobs; // linked list header
|
|
};
|
|
int conn_less(void *ca, void *cb);
|
|
void conn_setpos(void *c, size_t i);
|
|
void connsched(Conn *c);
|
|
void connclose(Conn *c);
|
|
void connsetproducer(Conn *c);
|
|
void connsetworker(Conn *c);
|
|
Job *connsoonestjob(Conn *c);
|
|
int conndeadlinesoon(Conn *c);
|
|
int conn_ready(Conn *c);
|
|
void conn_reserve_job(Conn *c, Job *j);
|
|
#define conn_waiting(c) ((c)->type & CONN_TYPE_WAITING)
|
|
|
|
|
|
|
|
|
|
enum
|
|
{
|
|
Filesizedef = (10 << 20)
|
|
};
|
|
|
|
struct Wal {
|
|
int filesize;
|
|
int use;
|
|
char *dir;
|
|
File *head;
|
|
File *cur;
|
|
File *tail;
|
|
int nfile;
|
|
int next;
|
|
int64 resv; // bytes reserved
|
|
int64 alive; // bytes in use
|
|
int64 nmig; // migrations
|
|
int64 nrec; // records written ever
|
|
int wantsync; // do we sync to disk?
|
|
int64 syncrate; // how often we sync to disk, in nanoseconds
|
|
int64 lastsync;
|
|
};
|
|
int waldirlock(Wal*);
|
|
void walinit(Wal*, Job *list);
|
|
int walwrite(Wal*, Job*);
|
|
void walmaint(Wal*);
|
|
int walresvput(Wal*, Job*);
|
|
int walresvupdate(Wal*);
|
|
void walgc(Wal*);
|
|
|
|
|
|
struct File {
|
|
File *next;
|
|
uint refs;
|
|
int seq;
|
|
int iswopen; // is open for writing
|
|
int fd;
|
|
int free;
|
|
int resv;
|
|
char *path;
|
|
Wal *w;
|
|
|
|
Job jlist; // jobs written in this file
|
|
};
|
|
int fileinit(File*, Wal*, int);
|
|
Wal* fileadd(File*, Wal*);
|
|
void fileincref(File*);
|
|
void filedecref(File*);
|
|
void fileaddjob(File*, Job*);
|
|
void filermjob(File*, Job*);
|
|
int fileread(File*, Job *list);
|
|
void filewopen(File*);
|
|
void filewclose(File*);
|
|
int filewrjobshort(File*, Job*);
|
|
int filewrjobfull(File*, Job*);
|
|
|
|
|
|
#define Portdef "11300"
|
|
|
|
struct Server {
|
|
char *port;
|
|
char *addr;
|
|
char *user;
|
|
|
|
Wal wal;
|
|
Socket sock;
|
|
|
|
// Connections that must produce deadline or timeout, ordered by the time.
|
|
Heap conns;
|
|
};
|
|
void srv_acquire_wal(Server *s);
|
|
void srvserve(Server *s);
|
|
void srvaccept(Server *s, int ev);
|