aboutsummaryrefslogtreecommitdiffstats
path: root/python/gevent/libev/libev_vfd.h
blob: ec16a2803e4f6909840fde625f31996c8ad6c388 (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
#ifdef _WIN32
#ifdef _WIN64
typedef PY_LONG_LONG vfd_socket_t;
#define vfd_socket_object PyLong_FromLongLong
#else
typedef long vfd_socket_t;
#define vfd_socket_object PyInt_FromLong
#endif
#ifdef LIBEV_EMBED
/*
 * If libev on win32 is embedded, then we can use an
 * arbitrary mapping between integer fds and OS
 * handles. Then by defining special macros libev
 * will use our functions.
 */

#define WIN32_LEAN_AND_MEAN
#include <winsock2.h>
#include <windows.h>

typedef struct vfd_entry_t
{
	vfd_socket_t handle; /* OS handle, i.e. SOCKET */
	int count; /* Reference count, 0 if free */
	int next; /* Next free fd, -1 if last */
} vfd_entry;

#define VFD_INCREMENT 128
static int vfd_num = 0; /* num allocated fds */
static int vfd_max = 0; /* max allocated fds */
static int vfd_next = -1; /* next free fd for reuse */
static PyObject* vfd_map = NULL; /* map OS handle -> virtual fd */
static vfd_entry* vfd_entries = NULL; /* list of virtual fd entries */

#ifdef WITH_THREAD
static CRITICAL_SECTION* volatile vfd_lock = NULL;
static CRITICAL_SECTION* vfd_make_lock()
{
	if (vfd_lock == NULL) {
		/* must use malloc and not PyMem_Malloc here */
		CRITICAL_SECTION* lock = malloc(sizeof(CRITICAL_SECTION));
		InitializeCriticalSection(lock);
		if (InterlockedCompareExchangePointer(&vfd_lock, lock, NULL) != NULL) {
			/* another thread initialized lock first */
			DeleteCriticalSection(lock);
			free(lock);
		}
	}
	return vfd_lock;
}
#define VFD_LOCK_ENTER  EnterCriticalSection(vfd_make_lock())
#define VFD_LOCK_LEAVE  LeaveCriticalSection(vfd_lock)
#define VFD_GIL_DECLARE PyGILState_STATE ___save
#define VFD_GIL_ENSURE  ___save = PyGILState_Ensure()
#define VFD_GIL_RELEASE PyGILState_Release(___save)
#else
#define VFD_LOCK_ENTER
#define VFD_LOCK_LEAVE
#define VFD_GIL_DECLARE
#define VFD_GIL_ENSURE
#define VFD_GIL_RELEASE
#endif

/*
 * Given a virtual fd returns an OS handle or -1
 * This function is speed critical, so it cannot use GIL
 */
static vfd_socket_t vfd_get(int fd)
{
	int handle = -1;
	VFD_LOCK_ENTER;
	if (vfd_entries != NULL && fd >= 0 && fd < vfd_num)
		handle = vfd_entries[fd].handle;
	VFD_LOCK_LEAVE;
	return handle;
}

#define EV_FD_TO_WIN32_HANDLE(fd) vfd_get((fd))

/*
 * Given an OS handle finds or allocates a virtual fd
 * Returns -1 on failure and sets Python exception if pyexc is non-zero
 */
static int vfd_open_(vfd_socket_t handle, int pyexc)
{
	VFD_GIL_DECLARE;
	int fd = -1;
	unsigned long arg;
	PyObject* key = NULL;
	PyObject* value;

	if (!pyexc) {
		VFD_GIL_ENSURE;
	}
	if (ioctlsocket(handle, FIONREAD, &arg) != 0) {
		if (pyexc)
			PyErr_Format(PyExc_IOError,
#ifdef _WIN64
				"%lld is not a socket (files are not supported)",
#else
				"%ld is not a socket (files are not supported)",
#endif
				handle);
		goto done;
	}
	if (vfd_map == NULL) {
		vfd_map = PyDict_New();
		if (vfd_map == NULL)
			goto done;
	}
	key = vfd_socket_object(handle);
	/* check if it's already in the dict */
	value = PyDict_GetItem(vfd_map, key);
	if (value != NULL) {
		/* is it safe to use PyInt_AS_LONG(value) here? */
		fd = PyInt_AsLong(value);
		if (fd >= 0) {
			++vfd_entries[fd].count;
			goto done;
		}
	}
	/* use the free entry, if available */
	if (vfd_next >= 0) {
		fd = vfd_next;
		vfd_next = vfd_entries[fd].next;
		VFD_LOCK_ENTER;
		goto allocated;
	}
	/* check if it would be out of bounds */
	if (vfd_num >= FD_SETSIZE) {
		/* libev's select doesn't support more that FD_SETSIZE fds */
		if (pyexc)
			PyErr_Format(PyExc_IOError, "cannot watch more than %d sockets", (int)FD_SETSIZE);
		goto done;
	}
	/* allocate more space if needed */
	VFD_LOCK_ENTER;
	if (vfd_num >= vfd_max) {
		int newsize = vfd_max + VFD_INCREMENT;
		vfd_entry* entries = PyMem_Realloc(vfd_entries, sizeof(vfd_entry) * newsize);
		if (entries == NULL) {
			VFD_LOCK_LEAVE;
			if (pyexc)
				PyErr_NoMemory();
			goto done;
		}
		vfd_entries = entries;
		vfd_max = newsize;
	}
	fd = vfd_num++;
allocated:
	/* vfd_lock must be acquired when entering here */
	vfd_entries[fd].handle = handle;
	vfd_entries[fd].count = 1;
	VFD_LOCK_LEAVE;
	value = PyInt_FromLong(fd);
	PyDict_SetItem(vfd_map, key, value);
	Py_DECREF(value);
done:
	Py_XDECREF(key);
	if (!pyexc) {
		VFD_GIL_RELEASE;
	}
	return fd;
}

#define vfd_open(fd) vfd_open_((fd), 1)
#define EV_WIN32_HANDLE_TO_FD(handle) vfd_open_((handle), 0)

static void vfd_free_(int fd, int needclose)
{
	VFD_GIL_DECLARE;
	PyObject* key;

	if (needclose) {
		VFD_GIL_ENSURE;
	}
	if (fd < 0 || fd >= vfd_num)
		goto done; /* out of bounds */
	if (vfd_entries[fd].count <= 0)
		goto done; /* free entry, ignore */
	if (!--vfd_entries[fd].count) {
		/* fd has just been freed */
		vfd_socket_t handle = vfd_entries[fd].handle;
		vfd_entries[fd].handle = -1;
		vfd_entries[fd].next = vfd_next;
		vfd_next = fd;
		if (needclose)
			closesocket(handle);
		/* vfd_map is assumed to be != NULL */
		key = vfd_socket_object(handle);
		PyDict_DelItem(vfd_map, key);
		Py_DECREF(key);
	}
done:
	if (needclose) {
		VFD_GIL_RELEASE;
	}
}

#define vfd_free(fd) vfd_free_((fd), 0)
#define EV_WIN32_CLOSE_FD(fd) vfd_free_((fd), 1)

#else
/*
 * If libev on win32 is not embedded in gevent, then
 * the only way to map vfds is to use the default of
 * using runtime fds in libev. Note that it will leak
 * fds, because there's no way of closing them safely
 */
#define vfd_get(fd) _get_osfhandle((fd))
#define vfd_open(fd) _open_osfhandle((fd), 0)
#define vfd_free(fd)
#endif
#else
/*
 * On non-win32 platforms vfd_* are noop macros
 */
typedef int vfd_socket_t;
#define vfd_get(fd) (fd)
#define vfd_open(fd) ((int)(fd))
#define vfd_free(fd)
#endif