-
Notifications
You must be signed in to change notification settings - Fork 116
/
Copy pathfifo.c
196 lines (163 loc) · 4.99 KB
/
fifo.c
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
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
#define DEBUG
#include <assert.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <pthread.h>
#include <signal.h>
#include <errno.h>
#include "util.h"
#include "debug.h"
#include "fifo.h"
/* Fifo queue to coordinate multiple pachi instances so that only one runs at
* a time. Having multiple multi-threaded pachis fight for cpu is not a good
* idea. Either run each one single threaded or use this.
*
* Implemented using shared memory segment + simple robust mutex:
* - dead-lock free, handles instances disappearing with the lock
* - ordering not guaranteed but almost 100% fifo in practice
*
* If your system uses systemd beware !
* systemd regularly cleans up what it thinks of as "stale" entries in
* /dev/shm so if you run Pachi in the background as non-system user shared
* memory will get broken in mysterious ways:
* https://superuser.com/questions/1117764/why-are-the-contents-of-dev-shm-is-being-removed-automatically
* Edit /etc/systemd/logind.conf and uncomment
* RemoveIPC=n
*
* For reference:
* - Real ticket-lock style implementation would give 100% guaranteed fifo
* order but not worth the extra complexity imo.
* - Semaphore looks nice for this on the surface (just what we need, no need
* for shm) but doesn't handle processes disappearing with the token
* -> timeout and it's a mess...
* - flock(): easy, may be ok for 2 tasks. Order completely unreliable
* (not fifo at all) as soon as you have 3 instances or more.
* - same for open(O_EXCL) */
/* Handle running pachi as different users ?
* Anyone will be able to attach memory segment. */
#define PACHI_FIFO_ALLOW_MULTIPLE_USERS 1
typedef struct {
pthread_mutex_t mutex;
} ticket_lock_t;
static void
ticket_init(ticket_lock_t *t)
{
pthread_mutexattr_t mattr;
pthread_mutexattr_init(&mattr);
pthread_mutexattr_setpshared(&mattr, PTHREAD_PROCESS_SHARED);
pthread_mutexattr_setrobust(&mattr, PTHREAD_MUTEX_ROBUST);
pthread_mutex_init(&t->mutex, &mattr);
}
/* Returns 0 if owner died */
static int
mutex_lock(pthread_mutex_t *mutex)
{
if ((errno = pthread_mutex_lock(mutex)) == 0)
return 1; /* All good. */
if (errno == EOWNERDEAD) {
//fprintf(stderr, "fifo: recovering dead mutex ...\n");
pthread_mutex_consistent(mutex);
return 0;
}
fail("pthread_mutex_lock");
return 0;
}
static void
mutex_unlock(pthread_mutex_t *mutex)
{
errno = pthread_mutex_unlock(mutex);
if (!errno) return; /* All good. */
fail("pthread_mutex_unlock");
}
static int
ticket_lock(ticket_lock_t *ticket)
{
if (!mutex_lock(&ticket->mutex)) /* Mutex owner died, recover... */
if (DEBUGL(2)) fprintf(stderr, "fifo: kicking stale instance\n");
return 0;
}
static void
ticket_unlock(ticket_lock_t *ticket, int me)
{
mutex_unlock(&ticket->mutex);
}
/***************************************************************************************************/
/* Shared memory */
#define SHM_NAME "pachi_fifo"
#define SHM_MAGIC ((int)0xf1f0c0de)
typedef struct {
unsigned int size;
int magic;
int ready;
int timestamp;
/* sched stuff */
ticket_lock_t queue;
} sched_shm_t;
static unsigned int shm_size = sizeof(sched_shm_t);
static sched_shm_t *shm = 0;
/* Create new shared memory segment */
static void
create_shm()
{
#ifdef PACHI_FIFO_ALLOW_MULTIPLE_USERS
umask(0); /* Any user may attach shared memory segment. */
int fd = shm_open(SHM_NAME, O_RDWR | O_CREAT | O_TRUNC, 0666);
umask(022);
#else
int fd = shm_open(SHM_NAME, O_RDWR | O_CREAT | O_TRUNC, 0644);
#endif
assert(fd != -1);
int r = ftruncate(fd, shm_size); assert(r == 0);
void *pt = mmap(0, shm_size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
assert(pt != MAP_FAILED);
shm = (sched_shm_t*)pt;
memset(shm, 0, sizeof(*shm));
shm->size = shm_size;
shm->magic = SHM_MAGIC;
shm->ready = 0;
shm->timestamp = time(NULL);
ticket_init(&shm->queue);
shm->ready = 1;
if (DEBUGL(2)) fprintf(stderr, "Fifo: created shared memory, id: %i\n", shm->timestamp);
}
/* Attach existing shared memory segment */
static int
attach_shm()
{
int fd = shm_open(SHM_NAME, O_RDWR, 0);
if (fd == -1) return 0; /* Doesn't exist yet... */
/* Sanity check, make sure it has the right size ... */
struct stat st;
if (stat("/dev/shm/" SHM_NAME, &st) != 0) fail("/dev/shm/" SHM_NAME);
assert(st.st_size == sizeof(sched_shm_t));
shm = (sched_shm_t*)mmap(0, shm_size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
if (shm == MAP_FAILED) fail("mmap");
assert(shm->magic == SHM_MAGIC);
assert(shm->size == shm_size);
assert(shm->ready);
if (DEBUGL(2)) fprintf(stderr, "Fifo: mapped shared memory, id: %i\n", shm->timestamp);
return 1;
}
/***************************************************************************************************/
void
fifo_init(void)
{
if (!attach_shm())
create_shm();
}
int
fifo_task_queue(void)
{
return ticket_lock(&shm->queue);
}
void
fifo_task_done(int ticket)
{
ticket_unlock(&shm->queue, ticket);
}