summaryrefslogtreecommitdiff
path: root/pflocal/connq.c
blob: bf93765261c38c3cd9d66097abe90471b0e5161c (plain)
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
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
/* Listen queue functions

   Copyright (C) 1995,96,2001,2012 Free Software Foundation, Inc.

   Written by Miles Bader <miles@gnu.org>

   This program is free software; you can redistribute it and/or
   modify it under the terms of the GNU General Public License as
   published by the Free Software Foundation; either version 2, or (at
   your option) any later version.

   This program is distributed in the hope that it will be useful, but
   WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
   General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with this program; if not, write to the Free Software
   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */

#include <pthread.h>
#include <assert.h>

#include "connq.h"

/* A queue for queueing incoming connections.  */
struct connq
{
  /* The connection request queue.  */
  struct connq_request *head;
  struct connq_request **tail;
  unsigned count;
  unsigned max;

  /* Threads that have done an accept on this queue wait on this condition.  */
  pthread_cond_t listeners;
  unsigned num_listeners;

  /* Threads that have done a connect on this queue wait on this condition.  */
  pthread_cond_t connectors;
  unsigned num_connectors;

  pthread_mutex_t lock;
};

/* ---------------------------------------------------------------- */

/* A data block allocated by a thread waiting on a connq, which is used to
   get information from and to the thread.  */
struct connq_request
{
  struct connq_request *next;

  /* The socket that's waiting to connect.  */
  struct sock *sock;
};

static inline void
connq_request_init (struct connq_request *req, struct sock *sock)
{
  req->sock = sock;
}

/* Enqueue connection request REQ onto CQ.  CQ must be locked.  */
static void
connq_request_enqueue (struct connq *cq, struct connq_request *req)
{
  assert (pthread_mutex_trylock (&cq->lock));

  req->next = NULL;
  *cq->tail = req;
  cq->tail = &req->next;

  cq->count ++;
}

/* Dequeue a pending request from CQ.  CQ must be locked and must not
   be empty.  */
static struct connq_request *
connq_request_dequeue (struct connq *cq)
{
  struct connq_request *req;

  assert (pthread_mutex_trylock (&cq->lock));
  assert (cq->head);

  req = cq->head;
  cq->head = req->next;
  if (! cq->head)
    /* We just dequeued the last element.  Fixup the tail pointer.  */
    cq->tail = &cq->head;

  cq->count --;

  return req;
}

/* ---------------------------------------------------------------- */

/* Create a new listening queue, returning it in CQ.  The resulting queue
   will be of zero length, that is it won't allow connections unless someone
   is already listening (change this with connq_set_length).  */
error_t
connq_create (struct connq **cq)
{
  struct connq *new = malloc (sizeof (struct connq));

  if (!new)
    return ENOBUFS;

  new->head = NULL;
  new->tail = &new->head;
  new->count = 0;
  /* By default, don't queue requests.  */
  new->max = 0;

  new->num_listeners = 0;
  new->num_connectors = 0;

  pthread_mutex_init (&new->lock, NULL);
  pthread_cond_init (&new->listeners, NULL);
  pthread_cond_init (&new->connectors, NULL);

  *cq = new;
  return 0;
}

/* Destroy a queue.  */
void
connq_destroy (struct connq *cq)
{
  /* Everybody in the queue should hold a reference to the socket
     containing the queue.  */
  assert (! cq->head);
  assert (cq->count == 0);

  free (cq);
}

/* ---------------------------------------------------------------- */

/* Return a connection request on CQ.  If SOCK is NULL, the request is
   left in the queue.  If NOBLOCK is true, EWOULDBLOCK is returned
   when there are no immediate connections available.  */
error_t
connq_listen (struct connq *cq, int noblock, struct sock **sock)
{
  error_t err = 0;

  pthread_mutex_lock (&cq->lock);

  if (noblock && cq->count == 0 && cq->num_connectors == 0)
    {
      pthread_mutex_unlock (&cq->lock);
      return EWOULDBLOCK;
    }

  if (! sock && (cq->count > 0 || cq->num_connectors > 0))
    /* The caller just wants to know if a connection ready.  */
    {
      pthread_mutex_unlock (&cq->lock);
      return 0;
    }

  cq->num_listeners++;

  if (cq->count == 0)
    /* The request queue is empty.  */
    {
      assert (! cq->head);

      if (cq->num_connectors > 0)
	/* Someone is waiting for an acceptor.  Signal that we can
	   service their request.  */
	pthread_cond_signal (&cq->connectors);

      do
	if (pthread_hurd_cond_wait_np (&cq->listeners, &cq->lock))
	  {
	    cq->num_listeners--;
	    err = EINTR;
	    goto out;
	  }
      while (cq->count == 0);
    }

  assert (cq->head);

  if (sock)
    /* Dequeue the next request, if desired.  */
    {
      struct connq_request *req = connq_request_dequeue (cq);
      *sock = req->sock;
      free (req);
    }
  else if (cq->num_listeners > 0)
    /* The caller will not actually process this request but someone
       else could.  (This case is rare but possible: it would require
       one thread to do a select on the socket and a second to do an
       accept.)  */
    pthread_cond_signal (&cq->listeners);
  else
    /* There is no one else to process the request and the connection
       has now been initiated.  This is not actually a problem as even
       if the current queue limit is 0, the connector will queue the
       request and another listener (should) eventually come along.
       (In fact it is very probably as the caller has likely done a
       select and will now follow up with an accept.)  */
    ;

 out:
  pthread_mutex_unlock (&cq->lock);
  return err;
}

/* Try to connect SOCK with the socket listening on CQ.  If NOBLOCK is
   true, then return EWOULDBLOCK if there are no connections
   immediately available.  On success, this call must be followed up
   either connq_connect_complete or connq_connect_cancel.  */
error_t
connq_connect (struct connq *cq, int noblock)
{
  pthread_mutex_lock (&cq->lock);

  /* Check for listeners after we've locked CQ for good.  */

  if (noblock
      && cq->count + cq->num_connectors >= cq->max + cq->num_listeners)
    /* We are in non-blocking mode and would have to wait to secure an
       entry in the listen queue.  */
    {
      pthread_mutex_unlock (&cq->lock);
      return EWOULDBLOCK;
    }

  cq->num_connectors ++;

  while (cq->count + cq->num_connectors > cq->max + cq->num_listeners)
    /* The queue is full and there is no immediate listener to service
       us.  Block until we can get a slot.  */
    if (pthread_hurd_cond_wait_np (&cq->connectors, &cq->lock))
      {
	cq->num_connectors --;
	pthread_mutex_unlock (&cq->lock);
	return EINTR;
      }

  pthread_mutex_unlock (&cq->lock);

  return 0;
}

/* Follow up to connq_connect.  Completes the connect, SOCK is the new
   server socket.  */
void
connq_connect_complete (struct connq *cq, struct sock *sock)
{
  struct connq_request *req;

  req = malloc (sizeof (struct connq_request));
  if (! req)
    abort ();

  connq_request_init (req, sock);

  pthread_mutex_lock (&cq->lock);

  assert (cq->num_connectors > 0);
  cq->num_connectors --;

  connq_request_enqueue (cq, req);

  if (cq->num_listeners > 0)
    /* Wake a listener up.  We must consume the listener ref here as
       someone else might call this function before the listener
       thread dequeues this request.  */
    {
      cq->num_listeners --;
      pthread_cond_signal (&cq->listeners);
    }

  pthread_mutex_unlock (&cq->lock);
}

/* Follow up to connq_connect.  Cancel the connect.  */
void
connq_connect_cancel (struct connq *cq)
{
  pthread_mutex_lock (&cq->lock);

  assert (cq->num_connectors > 0);
  cq->num_connectors --;

  if (cq->count + cq->num_connectors >= cq->max + cq->num_listeners)
    /* A connector is blocked and could use the spot we reserved.  */
    pthread_cond_signal (&cq->connectors);

  pthread_mutex_unlock (&cq->lock);
}

/* Set CQ's queue length to LENGTH.  */
error_t
connq_set_length (struct connq *cq, int max)
{
  int omax;

  pthread_mutex_lock (&cq->lock);
  omax = cq->max;
  cq->max = max;

  if (max > omax && cq->count >= omax && cq->count < max
      && cq->num_connectors >= cq->num_listeners)
    /* This is an increase in the number of connection slots which has
       made some slots available and there are waiting threads.  Wake
       them up.  */
    pthread_cond_broadcast (&cq->listeners);

  pthread_mutex_unlock (&cq->lock);

  return 0;
}