1 /*--------------------------------------------------------------------------
4 * Code to set up a dynamic shared memory segments and a specified
5 * number of background workers for shared memory message queue
8 * Copyright (C) 2013-2014, PostgreSQL Global Development Group
11 * src/test/modules/test_shm_mq/setup.c
13 * -------------------------------------------------------------------------
18 #include "miscadmin.h"
19 #include "postmaster/bgworker.h"
20 #include "storage/procsignal.h"
21 #include "storage/shm_toc.h"
22 #include "utils/memutils.h"
24 #include "test_shm_mq.h"
29 BackgroundWorkerHandle *handle[FLEXIBLE_ARRAY_MEMBER];
32 static void setup_dynamic_shared_memory(int64 queue_size, int nworkers,
34 test_shm_mq_header **hdrp,
35 shm_mq **outp, shm_mq **inp);
36 static worker_state *setup_background_workers(int nworkers,
38 static void cleanup_background_workers(dsm_segment *seg, Datum arg);
39 static void wait_for_workers_to_become_ready(worker_state *wstate,
40 volatile test_shm_mq_header *hdr);
41 static bool check_worker_status(worker_state *wstate);
44 * Set up a dynamic shared memory segment and zero or more background workers
48 test_shm_mq_setup(int64 queue_size, int32 nworkers, dsm_segment **segp,
49 shm_mq_handle **output, shm_mq_handle **input)
52 test_shm_mq_header *hdr;
53 shm_mq *outq = NULL; /* placate compiler */
54 shm_mq *inq = NULL; /* placate compiler */
57 /* Set up a dynamic shared memory segment. */
58 setup_dynamic_shared_memory(queue_size, nworkers, &seg, &hdr, &outq, &inq);
61 /* Register background workers. */
62 wstate = setup_background_workers(nworkers, seg);
64 /* Attach the queues. */
65 *output = shm_mq_attach(outq, seg, wstate->handle[0]);
66 *input = shm_mq_attach(inq, seg, wstate->handle[nworkers - 1]);
68 /* Wait for workers to become ready. */
69 wait_for_workers_to_become_ready(wstate, hdr);
72 * Once we reach this point, all workers are ready. We no longer need to
73 * kill them if we die; they'll die on their own as the message queues
76 cancel_on_dsm_detach(seg, cleanup_background_workers,
77 PointerGetDatum(wstate));
82 * Set up a dynamic shared memory segment.
84 * We set up a small control region that contains only a test_shm_mq_header,
85 * plus one region per message queue. There are as many message queues as
86 * the number of workers, plus one.
89 setup_dynamic_shared_memory(int64 queue_size, int nworkers,
90 dsm_segment **segp, test_shm_mq_header **hdrp,
91 shm_mq **outp, shm_mq **inp)
98 test_shm_mq_header *hdr;
100 /* Ensure a valid queue size. */
101 if (queue_size < 0 || ((uint64) queue_size) < shm_mq_minimum_size)
103 (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
104 errmsg("queue size must be at least %zu bytes",
105 shm_mq_minimum_size)));
106 if (queue_size != ((Size) queue_size))
108 (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
109 errmsg("queue size overflows size_t")));
112 * Estimate how much shared memory we need.
114 * Because the TOC machinery may choose to insert padding of oddly-sized
115 * requests, we must estimate each chunk separately.
117 * We need one key to register the location of the header, and we need
118 * nworkers + 1 keys to track the locations of the message queues.
120 shm_toc_initialize_estimator(&e);
121 shm_toc_estimate_chunk(&e, sizeof(test_shm_mq_header));
122 for (i = 0; i <= nworkers; ++i)
123 shm_toc_estimate_chunk(&e, (Size) queue_size);
124 shm_toc_estimate_keys(&e, 2 + nworkers);
125 segsize = shm_toc_estimate(&e);
127 /* Create the shared memory segment and establish a table of contents. */
128 seg = dsm_create(shm_toc_estimate(&e), 0);
129 toc = shm_toc_create(PG_TEST_SHM_MQ_MAGIC, dsm_segment_address(seg),
132 /* Set up the header region. */
133 hdr = shm_toc_allocate(toc, sizeof(test_shm_mq_header));
134 SpinLockInit(&hdr->mutex);
135 hdr->workers_total = nworkers;
136 hdr->workers_attached = 0;
137 hdr->workers_ready = 0;
138 shm_toc_insert(toc, 0, hdr);
140 /* Set up one message queue per worker, plus one. */
141 for (i = 0; i <= nworkers; ++i)
145 mq = shm_mq_create(shm_toc_allocate(toc, (Size) queue_size),
147 shm_toc_insert(toc, i + 1, mq);
151 /* We send messages to the first queue. */
152 shm_mq_set_sender(mq, MyProc);
157 /* We receive messages from the last queue. */
158 shm_mq_set_receiver(mq, MyProc);
163 /* Return results to caller. */
169 * Register background workers.
171 static worker_state *
172 setup_background_workers(int nworkers, dsm_segment *seg)
174 MemoryContext oldcontext;
175 BackgroundWorker worker;
176 worker_state *wstate;
180 * We need the worker_state object and the background worker handles to
181 * which it points to be allocated in CurTransactionContext rather than
182 * ExprContext; otherwise, they'll be destroyed before the on_dsm_detach
185 oldcontext = MemoryContextSwitchTo(CurTransactionContext);
187 /* Create worker state object. */
188 wstate = MemoryContextAlloc(TopTransactionContext,
189 offsetof(worker_state, handle) +
190 sizeof(BackgroundWorkerHandle *) * nworkers);
191 wstate->nworkers = 0;
194 * Arrange to kill all the workers if we abort before all workers are
195 * finished hooking themselves up to the dynamic shared memory segment.
197 * If we die after all the workers have finished hooking themselves up to
198 * the dynamic shared memory segment, we'll mark the two queues to which
199 * we're directly connected as detached, and the worker(s) connected to
200 * those queues will exit, marking any other queues to which they are
201 * connected as detached. This will cause any as-yet-unaware workers
202 * connected to those queues to exit in their turn, and so on, until
205 * But suppose the workers which are supposed to connect to the queues to
206 * which we're directly attached exit due to some error before they
207 * actually attach the queues. The remaining workers will have no way of
208 * knowing this. From their perspective, they're still waiting for those
209 * workers to start, when in fact they've already died.
211 on_dsm_detach(seg, cleanup_background_workers,
212 PointerGetDatum(wstate));
214 /* Configure a worker. */
215 worker.bgw_flags = BGWORKER_SHMEM_ACCESS;
216 worker.bgw_start_time = BgWorkerStart_ConsistentState;
217 worker.bgw_restart_time = BGW_NEVER_RESTART;
218 worker.bgw_main = NULL; /* new worker might not have library loaded */
219 sprintf(worker.bgw_library_name, "test_shm_mq");
220 sprintf(worker.bgw_function_name, "test_shm_mq_main");
221 snprintf(worker.bgw_name, BGW_MAXLEN, "test_shm_mq");
222 worker.bgw_main_arg = UInt32GetDatum(dsm_segment_handle(seg));
223 /* set bgw_notify_pid, so we can detect if the worker stops */
224 worker.bgw_notify_pid = MyProcPid;
226 /* Register the workers. */
227 for (i = 0; i < nworkers; ++i)
229 if (!RegisterDynamicBackgroundWorker(&worker, &wstate->handle[i]))
231 (errcode(ERRCODE_INSUFFICIENT_RESOURCES),
232 errmsg("could not register background process"),
233 errhint("You may need to increase max_worker_processes.")));
238 MemoryContextSwitchTo(oldcontext);
243 cleanup_background_workers(dsm_segment *seg, Datum arg)
245 worker_state *wstate = (worker_state *) DatumGetPointer(arg);
247 while (wstate->nworkers > 0)
250 TerminateBackgroundWorker(wstate->handle[wstate->nworkers]);
255 wait_for_workers_to_become_ready(worker_state *wstate,
256 volatile test_shm_mq_header *hdr)
264 /* If all the workers are ready, we have succeeded. */
265 SpinLockAcquire(&hdr->mutex);
266 workers_ready = hdr->workers_ready;
267 SpinLockRelease(&hdr->mutex);
268 if (workers_ready >= wstate->nworkers)
274 /* If any workers (or the postmaster) have died, we have failed. */
275 if (!check_worker_status(wstate))
281 /* Wait to be signalled. */
282 WaitLatch(MyLatch, WL_LATCH_SET, 0);
284 /* An interrupt may have occurred while we were waiting. */
285 CHECK_FOR_INTERRUPTS();
287 /* Reset the latch so we don't spin. */
293 (errcode(ERRCODE_INSUFFICIENT_RESOURCES),
294 errmsg("one or more background workers failed to start")));
298 check_worker_status(worker_state *wstate)
302 /* If any workers (or the postmaster) have died, we have failed. */
303 for (n = 0; n < wstate->nworkers; ++n)
305 BgwHandleStatus status;
308 status = GetBackgroundWorkerPid(wstate->handle[n], &pid);
309 if (status == BGWH_STOPPED || status == BGWH_POSTMASTER_DIED)
313 /* Otherwise, things still look OK. */