├── AUTHORS ├── MANIFEST ├── README.markdown ├── pyaio ├── __init__.py ├── core.c └── gevent.py ├── setup.py └── tests └── test_pyaio.py /AUTHORS: -------------------------------------------------------------------------------- 1 | Felipe Cruz 2 | Jason Fried 3 | -------------------------------------------------------------------------------- /MANIFEST: -------------------------------------------------------------------------------- 1 | # file GENERATED by distutils, do NOT edit 2 | setup.py 3 | pyaio/__init__.py 4 | pyaio/core.c 5 | pyaio/gevent.py 6 | -------------------------------------------------------------------------------- /README.markdown: -------------------------------------------------------------------------------- 1 | Python Asynchronous I/O bindings (aio.h) 2 | ======================================== 3 | 4 | Version 0.3 5 | **Linux only** 6 | 7 | You should wait for the callback to finish before queuing more requests in 8 | a tight loop. pyaio could hang if you hit the max aio queue size. 9 | 10 | Tuning 11 | ------- 12 | 13 | pyaio.aio_init(max threads, max aio queue, max thread sleep time) 14 | 15 | Linux Defaults to 20 threads and 64 queue size. 16 | Pyaio will use 5*cores and 4*threads instead of those values if larger. 17 | 18 | Reading 19 | ------- 20 | 21 | API 22 | 23 | ``python 24 | view = aio_read(fileno, file-offset, length, callback) 25 | `` 26 | 27 | Example 28 | 29 | ```python 30 | import pyaio 31 | import os 32 | 33 | def aio_callback(buf, rcode, errno): 34 | if rcode > 0: 35 | print 'python callback %s' % buf 36 | elif rcode == 0: 37 | print "EOF" 38 | else: 39 | print "Error: %d" % errno 40 | 41 | fd = os.open('/tmp/pyaio.txt', os.O_RDONLY) 42 | pyaio.aio_read(fd, 10, 20, aio_callback) 43 | ``` 44 | 45 | Writing 46 | ------- 47 | 48 | API 49 | 50 | ``python 51 | aio_write(fileno, buffer-object, file-offset, callback) 52 | `` 53 | 54 | Example 55 | 56 | ```python 57 | import pyaio 58 | import os 59 | 60 | def aio_callback(rt, errno): 61 | if rt > 0: 62 | print "Wrote %d bytes" % rt 63 | else: 64 | print "Got error: %d" % errno 65 | 66 | fd = os.open('/tmp/pyaio.txt', os.O_WRONLY) 67 | pyaio.aio_write(fd, "Writing Test.......", 30, aio_callback) 68 | ``` 69 | 70 | gevent Wrapper 71 | -------------- 72 | 73 | For a file() like wrapper around aio_read and aio_write using gevent 74 | a 'buffer' keyword argument to aioFile controls its internal buffer size 75 | 76 | ```python 77 | from pyaio.gevent import aioFile 78 | with aioFile('/tmp/pyaio.txt') as fr: 79 | data = fr.read() # Entire File 80 | 81 | with aioFile('/tmp/pyaio.txt', 'w') as fw: 82 | fw.write(data) 83 | ``` 84 | -------------------------------------------------------------------------------- /pyaio/__init__.py: -------------------------------------------------------------------------------- 1 | from .core import * 2 | 3 | from multiprocessing import cpu_count 4 | threads = cpu_count() * 5 5 | queue_size = threads * 4 6 | if threads > 20: 7 | aio_init(threads, queue_size, 1) 8 | 9 | -------------------------------------------------------------------------------- /pyaio/core.c: -------------------------------------------------------------------------------- 1 | #define PY_SSIZE_T_CLEAN 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | typedef struct py_aio_callback { 12 | struct aiocb *cb; 13 | PyObject *callback; 14 | Py_buffer buffer_view; 15 | PyObject *buffer; 16 | int8_t read; 17 | } Pyaio_cb; 18 | 19 | PyDoc_STRVAR(pyaio_read_doc, 20 | "aio_read(fileno, offset, len, callback)\n"); 21 | 22 | PyDoc_STRVAR(pyaio_write_doc, 23 | "aio_write(fileno, buffer, offset, callback)\n"); 24 | 25 | PyDoc_STRVAR(pyaio_init_doc, 26 | "aio_init(max_threads, max_queue_size, max_thread_idle_time)\n"); 27 | 28 | 29 | static int _async_callback(void *arg) 30 | { 31 | Pyaio_cb *aio = (Pyaio_cb *)arg; 32 | struct aiocb *cb; 33 | PyObject *callback, *args, *result, *buffer; 34 | Py_buffer *buffer_view; 35 | 36 | cb = aio->cb; 37 | callback = aio->callback; 38 | buffer_view = &(aio->buffer_view); 39 | buffer = aio->buffer; /* Should have ref count of +2 */ 40 | 41 | PyBuffer_Release(buffer_view); /* -1 refcount we are done with it */ 42 | 43 | if (aio->read) { 44 | if (aio_return(cb) > 0) { 45 | if (aio_return(cb) < cb->aio_nbytes) { 46 | PyByteArray_Resize(buffer, aio_return(cb)); 47 | } 48 | } 49 | else { 50 | PyByteArray_Resize(buffer, 0); 51 | } 52 | args = Py_BuildValue("(Oni)", buffer, aio_return(cb), aio_error(cb)); 53 | } 54 | else { /* WRITE */ 55 | args = Py_BuildValue("(ni)", aio_return(cb), aio_error(cb)); 56 | } 57 | result = PyObject_CallObject(callback, args); 58 | if (result == NULL) { 59 | printf("Exception in aio callback, dying!\n"); 60 | kill(getpid(), SIGKILL); // DIE FAST 61 | } 62 | Py_XDECREF(buffer); /* -1 refcount. we should be at 0 now */ 63 | Py_XDECREF(result); 64 | Py_XDECREF(callback); 65 | Py_XDECREF(args); 66 | free((struct aiocb *)cb); 67 | free(aio); 68 | return 0; 69 | } 70 | 71 | static void aio_completion_handler(sigval_t sigval) 72 | { 73 | Pyaio_cb *aio; 74 | aio = (Pyaio_cb*) sigval.sival_ptr; 75 | 76 | /* Hybrid Approach if Pending fails grab GIL do it directly */ 77 | if(Py_AddPendingCall(&_async_callback, aio) < 0) { 78 | PyGILState_STATE state = PyGILState_Ensure(); 79 | _async_callback(aio); 80 | PyGILState_Release(state); 81 | } 82 | 83 | return; 84 | } 85 | 86 | static PyObject * 87 | pyaio_init(PyObject *dummy, PyObject *args) { 88 | struct aioinit *init; 89 | int aio_threads, aio_num, aio_idle_time; 90 | 91 | Py_XINCREF(args); 92 | if (PyArg_ParseTuple(args, "iii", &aio_threads, &aio_num, &aio_idle_time)) { 93 | init = malloc(sizeof(struct aioinit)); 94 | if (!init) 95 | return PyErr_NoMemory(); 96 | init->aio_threads = aio_threads; 97 | init->aio_num = aio_num; 98 | init->aio_idle_time = aio_idle_time; 99 | aio_init(init); 100 | free(init); 101 | } 102 | Py_XDECREF(args); 103 | Py_XINCREF(Py_None); 104 | return Py_None; 105 | } 106 | 107 | 108 | static PyObject * 109 | pyaio_read(PyObject *dummy, PyObject *args) { 110 | 111 | int fd; 112 | Py_ssize_t numbytes, offset, ret; 113 | Pyaio_cb *aio; 114 | PyObject *callback, *return_, *buffer; 115 | Py_buffer *buffer_view; 116 | 117 | Py_XINCREF(args); 118 | if (PyArg_ParseTuple(args, "innO:set_callback", &fd, &offset, &numbytes, 119 | &callback)) { 120 | if (!PyCallable_Check(callback)) { 121 | PyErr_SetString(PyExc_TypeError, 122 | "parameter must be callable"); 123 | return NULL; 124 | } 125 | Py_XINCREF(callback); /* Add a reference to new callback */ 126 | } 127 | Py_XDECREF(args); 128 | aio = malloc(sizeof(Pyaio_cb)); 129 | if (!aio) 130 | return PyErr_NoMemory(); 131 | buffer_view = &(aio->buffer_view); 132 | 133 | aio->cb = malloc(sizeof(struct aiocb)); 134 | if (!aio->cb) 135 | return PyErr_NoMemory(); 136 | bzero((char *) aio->cb, sizeof(struct aiocb)); 137 | 138 | /* Empty ByteArray of the requested read size INCREF*/ 139 | buffer = PyByteArray_FromStringAndSize(NULL, numbytes); 140 | /* Get the buffer view / put it in the aio struct INCREF */ 141 | PyObject_GetBuffer(buffer, buffer_view, PyBUF_CONTIG); 142 | 143 | aio->callback = callback; 144 | aio->buffer = buffer; 145 | aio->read = 1; /* Read Operation */ 146 | 147 | aio->cb->aio_buf = buffer_view->buf; 148 | aio->cb->aio_fildes = fd; 149 | aio->cb->aio_nbytes = buffer_view->len; 150 | aio->cb->aio_offset = offset; 151 | aio->cb->aio_sigevent.sigev_notify = SIGEV_THREAD; /* EvIL */ 152 | aio->cb->aio_sigevent.sigev_notify_attributes = NULL; 153 | aio->cb->aio_sigevent.sigev_notify_function = aio_completion_handler; 154 | aio->cb->aio_sigevent.sigev_value.sival_ptr = aio; 155 | 156 | ret = aio_read(aio->cb); 157 | 158 | if (ret < 0) { 159 | return PyErr_SetFromErrno(PyExc_IOError); 160 | } 161 | else { 162 | return_ = Py_BuildValue("n", ret); 163 | return return_; 164 | } 165 | } 166 | 167 | static PyObject * 168 | pyaio_write(PyObject *dummy, PyObject *args) { 169 | 170 | PyObject *buffer; 171 | Py_buffer *buffer_view; 172 | 173 | int fd; 174 | Py_ssize_t offset, ret; 175 | 176 | Pyaio_cb *aio; 177 | PyObject *callback, *return_; 178 | Py_XINCREF(args); 179 | if (PyArg_ParseTuple(args, "iOnO:set_callback", &fd, &buffer, 180 | &offset, &callback)) { 181 | if (!PyCallable_Check(callback)) { 182 | PyErr_SetString(PyExc_TypeError, 183 | "parameter must be callable"); 184 | return NULL; 185 | } 186 | if (!PyObject_CheckBuffer(buffer)) { 187 | PyErr_SetString(PyExc_TypeError, 188 | "write buffer must support buffer interface"); 189 | return NULL; 190 | } 191 | Py_XINCREF(callback); /* Add a reference to new callback */ 192 | Py_XINCREF(buffer); 193 | } 194 | Py_XDECREF(args); 195 | 196 | aio = malloc(sizeof(Pyaio_cb)); 197 | buffer_view = &(aio->buffer_view); 198 | /* Get a Buffer INCREF */ 199 | PyObject_GetBuffer(buffer, buffer_view, PyBUF_CONTIG_RO); 200 | 201 | 202 | aio->cb = malloc(sizeof(struct aiocb)); 203 | bzero((void *) aio->cb, sizeof(struct aiocb)); 204 | aio->read = 0; /* Write Operation */ 205 | aio->callback = callback; 206 | aio->buffer = buffer; 207 | aio->cb->aio_buf = buffer_view->buf; 208 | aio->cb->aio_fildes = fd; 209 | aio->cb->aio_nbytes = buffer_view->len; 210 | aio->cb->aio_offset = offset; 211 | aio->cb->aio_sigevent.sigev_notify = SIGEV_THREAD; 212 | aio->cb->aio_sigevent.sigev_notify_attributes = NULL; 213 | aio->cb->aio_sigevent.sigev_notify_function = aio_completion_handler; 214 | aio->cb->aio_sigevent.sigev_value.sival_ptr = aio; 215 | 216 | ret = aio_write(aio->cb); 217 | 218 | if (ret < 0) { 219 | return PyErr_SetFromErrno(PyExc_IOError); 220 | } 221 | else { 222 | return_ = Py_BuildValue("n", ret); 223 | return return_; 224 | } 225 | } 226 | 227 | static PyMethodDef PyaioMethods[] = { 228 | 229 | { "aio_read", pyaio_read, 230 | METH_VARARGS, pyaio_read_doc }, 231 | 232 | { "aio_write", pyaio_write, 233 | METH_VARARGS, pyaio_write_doc }, 234 | 235 | { "aio_init", pyaio_init, 236 | METH_VARARGS, pyaio_init_doc }, 237 | 238 | { NULL, NULL, 0, NULL } 239 | }; 240 | #if PY_MAJOR_VERSION >= 3 241 | static struct PyModuleDef moduledef = { 242 | PyModuleDef_HEAD_INIT, 243 | "core", /* m_name */ 244 | "Python POSIX aio (aio.h) bindings", /* m_doc */ 245 | -1, /* m_size */ 246 | PyaioMethods, /* m_methods */ 247 | NULL, /* m_reload */ 248 | NULL, /* m_traverse */ 249 | NULL, /* m_clear */ 250 | NULL, /* m_free */ 251 | }; 252 | #endif 253 | 254 | PyObject * 255 | init_pyaio(void) { 256 | PyObject *m; 257 | PyObject *__version__; 258 | 259 | /* We will be using threads */ 260 | PyEval_InitThreads(); 261 | #if PY_MAJOR_VERSION >= 3 262 | __version__ = PyUnicode_FromFormat("%s", PYAIO_VERSION); 263 | #else 264 | __version__ = PyString_FromFormat("%s", PYAIO_VERSION); 265 | #endif 266 | 267 | if (!__version__) { 268 | return NULL; 269 | } 270 | 271 | #if PY_MAJOR_VERSION >= 3 272 | m = PyModule_Create(&moduledef); 273 | #else 274 | m = Py_InitModule3("core", PyaioMethods, NULL); 275 | #endif 276 | 277 | if (!m) { 278 | Py_DECREF(__version__); 279 | return NULL; 280 | } 281 | 282 | if (PyModule_AddObject(m, "__version__", __version__)) { 283 | Py_DECREF(__version__); 284 | Py_DECREF(m); 285 | return NULL; 286 | } 287 | 288 | return m; 289 | } 290 | 291 | #if PY_MAJOR_VERSION >= 3 292 | PyMODINIT_FUNC 293 | PyInit_core(void) 294 | { 295 | return init_pyaio(); 296 | } 297 | #else 298 | PyMODINIT_FUNC initcore(void) { 299 | init_pyaio(); 300 | } 301 | #endif 302 | -------------------------------------------------------------------------------- /pyaio/gevent.py: -------------------------------------------------------------------------------- 1 | from __future__ import absolute_import 2 | import os 3 | import pyaio 4 | import gevent 5 | from gevent.event import AsyncResult 6 | from gevent.coros import RLock 7 | 8 | def _keep_awake(): 9 | while True: 10 | gevent.sleep(0.001) 11 | 12 | class aioFile(object): 13 | """a buffered File like object that uses pyaio and gevent""" 14 | def __init__(self, filename, mode='r', buffer=16<<10): 15 | modes = os.O_LARGEFILE | os.O_CREAT 16 | self._offset = 0 17 | self._buffer_size = buffer 18 | if buffer: 19 | self._buffer_lock = RLock() 20 | self._read = False 21 | self._write = False 22 | self._read_buf = None 23 | self._write_buf = None 24 | self._eof = False # Optimization to limit calls 25 | self._append = False # Append Mode writes ignore offset 26 | self._stay_alive = gevent.spawn(_keep_awake); 27 | if mode.startswith('r') or '+' in mode: 28 | self._read = True 29 | self._read_buf = bytearray() 30 | if '+' not in mode: 31 | modes |= os.O_RDONLY 32 | if mode.startswith('w') or mode.startswith('a') or '+' in mode: 33 | if mode.startswith('w'): 34 | modes |= os.O_TRUNC 35 | self._write = True 36 | self._write_buf = bytearray() 37 | self._flush = False 38 | if '+' not in mode: 39 | modes |= os.O_WRONLY 40 | if '+' in mode: 41 | modes |= os.O_RDWR 42 | if mode.startswith('a'): 43 | modes |= os.O_APPEND 44 | self._append = True 45 | self._fd = os.open(filename, modes) 46 | 47 | def _clear_read_buf(self): 48 | if self._read: 49 | self._eof = False 50 | del self._read_buf[0:] 51 | 52 | def __enter__(self): 53 | return self 54 | 55 | def __exit__(self, exc_type, exc_value, traceback): 56 | self.close() 57 | 58 | def close(self): 59 | self.flush() 60 | os.close(self._fd) 61 | self._stay_alive.kill() 62 | 63 | def stat(self): 64 | return os.fstat(self._fd) 65 | 66 | def seek(self, pos, how=os.SEEK_SET): 67 | """Change the file pos, will clear read cache and flush writes """ \ 68 | """This will also clear the EOF flag for the file""" 69 | offset = self._offset 70 | if how != os.SEEK_CUR and how != os.SEEK_END and how != os.SEEK_SET: 71 | raise OSError(14, 72 | 'Invalid seek point use os.SEEK_SET, os.SEEK_CUR, os.SEEK_END') 73 | if how == os.SEEK_CUR: 74 | offset += pos 75 | elif how == os.SEEK_END: 76 | #Ugh this could be harry if we have outstanding writes 77 | offset = self.stat().st_size + pos 78 | else: 79 | offset = pos 80 | if offset < 0: 81 | raise OSError(14, 'File Position invalid, less than 0') 82 | #Even if the pos didn't change fix the buffers and EOF 83 | self._clear_read_buf() 84 | if not self._append: # DON'T FLUSH on seek with append 85 | self.flush() 86 | self._offset = offset 87 | return offset 88 | 89 | def flush(self): 90 | """Flush write buffer""" 91 | if self._write and self._buffer_size: 92 | self._flush = True 93 | while len(self._write_buf): 94 | self.write(None) 95 | self._flush = False 96 | 97 | def _read_file(self): 98 | fbuf = bytearray() 99 | while True: 100 | part = self.read(16 << 10) # Read 16k 101 | if part is None: # EOF 102 | break 103 | fbuf.extend(part) 104 | return fbuf 105 | 106 | def write(self, buf, offset=None): 107 | """write a buffer object to file""" 108 | if not self._write: 109 | raise IOError(9, 'Bad file descriptor') 110 | if not self._append and self._buffer_size and self._read_buf: 111 | # We should clear read cache 112 | self._clear_read_buf() 113 | if offset is None: 114 | offset = self._offset 115 | write_size = self._buffer_size 116 | if not self._buffer_size and buf: 117 | write_size = len(buf) 118 | if not self._append and offset != self._offset: 119 | self.seek(offset) # Makes sure we write our buffer 120 | 121 | #If we buffer we use the global buffer if not we use a local buffer 122 | if self._buffer_size: 123 | lbuf = self._write_buf 124 | self._buffer_lock.acquire() 125 | if buf: 126 | # The a memoryview of the buffer 127 | lbuf.extend(buf) # pushed to pyaio so we need to lock 128 | else: 129 | lbuf = buf 130 | 131 | while lbuf and len(lbuf) >= self._buffer_size \ 132 | or (self._flush and lbuf): 133 | result = AsyncResult() 134 | def _write_results(rcode, errno): 135 | result.set((rcode, errno)) 136 | pyaio.aio_write(self._fd, memoryview(lbuf)[0:write_size], 137 | offset, _write_results) 138 | rcode, errno = result.get() #SLEEP 139 | 140 | if rcode < 0: # Some kind of error 141 | raise IOError(errno, 'AIO Write Error %d' % errno) 142 | # Clean up buffer (of actually written bytes) 143 | if self._buffer_size: 144 | del lbuf[0:rcode] 145 | else: 146 | lbuf = None 147 | self._offset = offset = offset + rcode # Move the file offset 148 | if self._buffer_size: 149 | self._buffer_lock.release() 150 | if buf: 151 | return len(buf) 152 | else: 153 | return 0 154 | 155 | def read(self, size=0, offset=None): 156 | """read a size of bytes from the file, or entire file if 0 """ \ 157 | """for speed we assume EOF after first short read""" 158 | if not self._read: 159 | raise IOError(9, 'Bad file descriptor') 160 | if not self._append and self._buffer_size and self._write_buf: 161 | self.flush() 162 | if offset is None: 163 | offset = self._offset 164 | if offset != self._offset: 165 | self.seek(offset) # To make sure we blow away our read cache 166 | if size == 0: # Attempt to read entire file and return in a single return 167 | return self._read_file() 168 | else: 169 | rbuf = bytearray() # Holding Place for multiple reads 170 | while len(rbuf) < size: # People get what they ask for 171 | # If we don't want to buffer then just read what they want 172 | if len(self._read_buf) < size - len(rbuf) and not self._eof: 173 | #Ok we are buffer short so lets do a read 174 | result = AsyncResult() 175 | def _read_results(buf, rcode, errno): 176 | result.set((buf, rcode, errno)) 177 | read_size = size - len(rbuf) 178 | if self._buffer_size: # If we buffer read buffer instead 179 | read_size = self._buffer_size 180 | pyaio.aio_read(self._fd, offset, read_size, _read_results) 181 | buf, rcode, errno = result.get() #SLEEP 182 | if rcode < 0: # Some kind of error 183 | raise IOError(errno, 'AIO Read Error %d' % errno) 184 | #Rcode will be the bytes read so lets push the offset 185 | self._offset = offset = offset + rcode 186 | if self._buffer_size: 187 | self._read_buf.extend(buf) 188 | else: 189 | rbuf = buf # Pass through because we are not buffering 190 | if rcode == 0 or rcode < read_size: # Good Enough 191 | self._eof = True 192 | #Do a buffer read 193 | toread = size - len(rbuf) 194 | if self._buffer_size: 195 | rbuf.extend(memoryview(self._read_buf)[0:toread]) 196 | #Clean up read buffer 197 | del self._read_buf[0:toread] 198 | if not self._read_buf and self._eof: # Empty buffer and eof 199 | break 200 | if self._eof and not rbuf: 201 | return None #EOF NO DATA 202 | else: 203 | return rbuf 204 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from distutils.core import setup, Extension 2 | import sys 3 | 4 | if sys.version < '2.7' and not sys.version[0] == "3": 5 | print("pyaio requires python of at least version 2.7 to build correctly") 6 | 7 | version = '0.4' 8 | 9 | pyaiocore = \ 10 | Extension('pyaio.core', 11 | sources = ['pyaio/core.c'], 12 | libraries = ['rt'], 13 | extra_compile_args = ['-D_FILE_OFFSET_BITS=64'], 14 | define_macros=[('PYAIO_VERSION', '"{0}"'.format(version))]) 15 | 16 | setup( 17 | name = 'pyaio', 18 | version = version, 19 | description = 'Python Asynchronous I/O bindings (aio.h)', 20 | author = 'Felipe Cruz', 21 | author_email = 'felipecruz@loogica.net', 22 | url = 'https://github.com/felipecruz/pyaio', 23 | classifiers = [ 24 | 'Operating System :: POSIX', 25 | 'Development Status :: 4 - Beta', 26 | 'License :: OSI Approved :: BSD License' 27 | ], 28 | ext_modules = [pyaiocore], 29 | py_modules = ['pyaio.gevent']) 30 | 31 | 32 | -------------------------------------------------------------------------------- /tests/test_pyaio.py: -------------------------------------------------------------------------------- 1 | import pyaio 2 | import time 3 | import os 4 | 5 | class hax(object): 6 | def __init__(self): 7 | self.x = 0 8 | 9 | def test_aio_read(): 10 | s = hax() 11 | def callback(buf, rt, er): 12 | assert buf == b"#define PY_SSIZE_T_CLEAN" 13 | assert rt == len(buf) 14 | assert er == 0 15 | s.x -= 1 16 | 17 | fileno = os.open('./pyaio/core.c', os.O_RDONLY) 18 | s.x += 1 19 | ret = pyaio.aio_read(fileno, 0, 24, callback) 20 | assert ret == 0 21 | while(s.x != 0): 22 | time.sleep(0.05) 23 | 24 | def test_aio_read_stress(): 25 | s = hax() 26 | def callback(buf, rt, er): 27 | assert buf == b"#define PY_SSIZE_T_CLEAN" 28 | assert rt == len(buf) 29 | assert er == 0 30 | s.x -= 1 31 | 32 | fileno = os.open('./pyaio/core.c', os.O_RDONLY) 33 | for x in range(1000): 34 | s.x += 1 35 | ret = pyaio.aio_read(fileno, 0, 24, callback) 36 | time.sleep(0.0001) 37 | assert ret == 0 38 | 39 | while(s.x != 0): 40 | time.sleep(0.05) 41 | 42 | def test_aio_write(): 43 | s = hax() 44 | def callback2(rt, er): 45 | assert rt == 10 46 | assert er == 0 47 | f = open('/tmp/c.txt', 'r') 48 | content = f.read() 49 | assert content == "pyaiopyaio" 50 | s.x -= 1 51 | 52 | fileno = os.open('/tmp/c.txt', os.O_WRONLY | os.O_CREAT | os.O_TRUNC) 53 | s.x += 1 54 | ret = pyaio.aio_write(fileno, b"pyaiopyaio", 0, callback2) 55 | assert ret == 0 56 | 57 | while(s.x != 0): 58 | time.sleep(0.05) 59 | 60 | def test_aio_write_read_stress(): 61 | s = hax() 62 | def callback2(rt, er): 63 | assert rt == 10 64 | assert er == 0 65 | s.x -= 1 66 | 67 | def callback(buf, rt, er): 68 | if rt > 0: 69 | assert len(buf) == rt 70 | else: 71 | # EOF 72 | assert rt == 0 73 | assert er == 0 74 | s.x -= 1 75 | 76 | fileno = os.open('/tmp/c.txt', os.O_RDWR | os.O_CREAT | os.O_TRUNC) 77 | # These could hit in any order so its not safe to say the 78 | for x in range(1000): 79 | s.x += 1 80 | ret = pyaio.aio_write(fileno, 81 | b"pyaiopyaio", x * 10, callback2) 82 | assert ret == 0 83 | time.sleep(0.0001) 84 | s.x += 1 85 | ret = pyaio.aio_read(fileno, x * 10, 10, callback) 86 | assert ret == 0 87 | time.sleep(0.0001) 88 | while(s.x != 0): 89 | time.sleep(0.05) 90 | 91 | def run(method): 92 | ts = time.time() 93 | print("Running %s" % method.__name__) 94 | method() 95 | print("Passed!") 96 | print("Total Time (%.2f)." % (time.time() - ts)) 97 | 98 | if __name__ == '__main__': 99 | run(test_aio_read) 100 | run(test_aio_write) 101 | run(test_aio_read_stress) 102 | run(test_aio_write_read_stress) 103 | --------------------------------------------------------------------------------