summaryrefslogtreecommitdiff
path: root/ot_fdstate.c
blob: 6ddbea30f21abf313063c19f4994e44c58e71621 (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
/* This software was written by Dirk Engling <erdgeist@erdgeist.org>
   It is considered beerware. Prost. Skol. Cheers or whatever.

   $id$ */

/* System */
#include <stdatomic.h>
#include <stdint.h>
#include <stdlib.h>
#include <sys/mman.h>
#include <sys/resource.h>
#include <sys/types.h>

/* Opentracker */
#include "ot_fdstate.h"

enum { FD_FREE, FD_IDLE, FD_BUSY, FD_CLOSING };

/* Generation and state share one word so that taking the state is also a
   check that the generation has not moved */
#define FD_GEN(w)       ((uint32_t)((w) >> 32))
#define FD_ST(w)        ((uint32_t)(((w) >> 24) & 0xff))
#define FD_WORD(g, s)   (((uint64_t)(g) << 32) | ((uint64_t)(s) << 24))
#define FD_SET_ST(w, s) (((w) & ~(uint64_t)0xff000000) | ((uint64_t)(s) << 24))

typedef struct {
  _Atomic uint64_t state;
  _Atomic(void *)  cookie;
  _Atomic ot_time  expiry;
} ot_fdslot;

/* A descriptor the kernel can hand out is below the limit it was handed out
   under, but the limit can be raised after we sized the table, so an out of
   range descriptor is rejected */
#define FD_SLOTS_MAX (1u << 20)

static ot_fdslot *g_slots;
static size_t     g_slot_count;

int fdstate_init(void) {
  struct rlimit limit;
  size_t        count = FD_SLOTS_MAX;

  if (getrlimit(RLIMIT_NOFILE, &limit) == 0 && limit.rlim_cur != RLIM_INFINITY && limit.rlim_cur < FD_SLOTS_MAX)
    count = (size_t)limit.rlim_cur;

  g_slots = mmap(NULL, count * sizeof(ot_fdslot), PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
  if (g_slots == MAP_FAILED) {
    g_slots = NULL;
    return -1;
  }

  g_slot_count = count;
  return 0;
}

void fdstate_deinit(void) {
  if (g_slots)
    munmap(g_slots, g_slot_count * sizeof(ot_fdslot));
  g_slots      = NULL;
  g_slot_count = 0;
}

size_t fdstate_slots(void) { return g_slot_count; }

static ot_fdslot *fdstate_slot(int fd) {
  if (fd < 0 || (size_t)fd >= g_slot_count)
    return NULL;
  return g_slots + fd;
}

uint32_t fdstate_open(int fd, void *cookie, ot_time expiry) {
  ot_fdslot *slot = fdstate_slot(fd);
  uint64_t   word;

  if (!slot)
    return 0;

  word = atomic_load_explicit(&slot->state, memory_order_relaxed);
  atomic_store_explicit(&slot->cookie, cookie, memory_order_relaxed);
  atomic_store_explicit(&slot->expiry, expiry, memory_order_relaxed);
  /* Publishes the two above to whoever claims the slot next */
  atomic_store_explicit(&slot->state, FD_WORD(FD_GEN(word), FD_IDLE), memory_order_release);

  return FD_GEN(word);
}

int fdstate_claim(int fd, uint32_t generation) {
  ot_fdslot *slot = fdstate_slot(fd);
  uint64_t   word;

  if (!slot)
    return 0;

  word = atomic_load_explicit(&slot->state, memory_order_acquire);
  for (;;) {
    if (FD_GEN(word) != generation || FD_ST(word) != FD_IDLE)
      return 0;
    if (atomic_compare_exchange_weak_explicit(&slot->state, &word, FD_SET_ST(word, FD_BUSY), memory_order_acquire,
                                              memory_order_relaxed))
      return 1;
    /* word now holds what was really there, so the test above runs again */
  }
}

void fdstate_release(int fd) {
  ot_fdslot *slot = fdstate_slot(fd);
  uint64_t   word;

  if (!slot)
    return;

  word = atomic_load_explicit(&slot->state, memory_order_relaxed);
  atomic_store_explicit(&slot->state, FD_SET_ST(word, FD_IDLE), memory_order_release);
}

int fdstate_claim_expired(int fd, ot_time now) {
  ot_fdslot *slot = fdstate_slot(fd);
  uint64_t   word;
  ot_time    expiry;

  if (!slot)
    return 0;

  /* Reading before asking for the slot keeps the common case, a descriptor
     that has not expired, off the bus entirely */
  word = atomic_load_explicit(&slot->state, memory_order_relaxed);
  if (FD_ST(word) != FD_IDLE)
    return 0;
  expiry = atomic_load_explicit(&slot->expiry, memory_order_relaxed);
  if (!expiry || expiry > now)
    return 0;

  /* Nothing can move the expiry while the slot sits at FD_IDLE, so winning
     the slot is the last word */
  return atomic_compare_exchange_strong_explicit(&slot->state, &word, FD_SET_ST(word, FD_CLOSING), memory_order_acquire,
                                                 memory_order_relaxed);
}

void fdstate_close(int fd) {
  ot_fdslot *slot = fdstate_slot(fd);
  uint64_t   word;

  if (!slot)
    return;

  word = atomic_load_explicit(&slot->state, memory_order_relaxed);
  atomic_store_explicit(&slot->cookie, NULL, memory_order_relaxed);
  atomic_store_explicit(&slot->expiry, 0, memory_order_relaxed);
  atomic_store_explicit(&slot->state, FD_WORD(FD_GEN(word) + 1, FD_FREE), memory_order_release);
}

void *fdstate_cookie(int fd) {
  ot_fdslot *slot = fdstate_slot(fd);

  return slot ? atomic_load_explicit(&slot->cookie, memory_order_relaxed) : NULL;
}

void fdstate_set_expiry(int fd, ot_time expiry) {
  ot_fdslot *slot = fdstate_slot(fd);

  if (slot)
    atomic_store_explicit(&slot->expiry, expiry, memory_order_relaxed);
}