├── .github └── workflows │ ├── build-wheels.yaml │ └── pypi-deploy.yaml ├── .gitignore ├── INSTALL ├── LICENSE ├── MANIFEST.in ├── README.md ├── USAGE.md ├── VERSION ├── build_support ├── __init__.py ├── discover_system_info.py └── src │ ├── sniff_mq_existence.c │ ├── sniff_mq_prio_max.c │ ├── sniff_page_size.c │ ├── sniff_realtime_lib.c │ ├── sniff_sem_getvalue.c │ ├── sniff_sem_timedwait.c │ └── sniff_sem_value_max.c ├── building.md ├── demos ├── demo1 │ ├── ReadMe.md │ ├── SampleIpcConversation.png │ ├── cleanup.py │ ├── conclusion.c │ ├── conclusion.py │ ├── make_all.sh │ ├── md5.c │ ├── md5.h │ ├── params.txt │ ├── premise.c │ ├── premise.py │ ├── utils.c │ ├── utils.h │ └── utils.py ├── demo2 │ ├── ReadMe.md │ ├── SampleIpcConversation.png │ ├── cleanup.py │ ├── conclusion.py │ ├── params.txt │ ├── premise.py │ └── utils.py ├── demo3 │ ├── ReadMe.md │ ├── cleanup.py │ ├── one_shot_signal.py │ ├── one_shot_thread.py │ ├── repeating_signal.py │ ├── repeating_thread.py │ └── utils.py ├── demo4 │ ├── ReadMe.md │ ├── child.py │ └── parent.py └── demo5 │ ├── ReadMe.md │ ├── listener.py │ └── transmitter.py ├── history.md ├── memory_leak_tests.py ├── pyproject.toml ├── sem_getvalue_test.c ├── setup.py ├── src └── posix_ipc_module.c └── tests ├── __init__.py ├── base.py ├── test_memory.py ├── test_message_queues.py ├── test_module.py └── test_semaphores.py /.github/workflows/build-wheels.yaml: -------------------------------------------------------------------------------- 1 | name: Build and test wheels 2 | 3 | on: 4 | push: 5 | branches: 6 | - develop 7 | - master 8 | pull_request: 9 | workflow_call: 10 | 11 | concurrency: 12 | group: ${{ github.workflow }}-${{ github.ref }}-build 13 | cancel-in-progress: true 14 | 15 | jobs: 16 | build_wheels: 17 | name: ${{ matrix.os }} 18 | runs-on: ${{ matrix.os }} 19 | strategy: 20 | fail-fast: false 21 | matrix: 22 | # macos-13 is an intel runner, macos-14 is apple silicon 23 | os: [ubuntu-latest, ubuntu-24.04-arm, macos-13, macos-14] 24 | 25 | steps: 26 | - uses: actions/checkout@v4 27 | 28 | - name: Build and test wheels 29 | uses: pypa/cibuildwheel@v2.23.0 30 | env: 31 | CIBW_ENABLE: pypy 32 | CIBW_TEST_COMMAND: python -m unittest discover --start-directory {package} 33 | 34 | - uses: actions/upload-artifact@v4 35 | with: 36 | name: cibw-wheels-${{ matrix.os }} 37 | path: ./wheelhouse/*.whl 38 | -------------------------------------------------------------------------------- /.github/workflows/pypi-deploy.yaml: -------------------------------------------------------------------------------- 1 | name: Build, test, and upload wheels to PyPI 2 | 3 | on: 4 | push: 5 | tags: 6 | # only trigger on tags that match the pattern 7 | # rel.. 8 | - "rel[0-9]+.[0-9]+.[0-9]+" 9 | 10 | jobs: 11 | build_wheels: 12 | uses: ./.github/workflows/build-wheels.yaml 13 | 14 | build_sdist: 15 | name: Build source distribution 16 | runs-on: ubuntu-latest 17 | steps: 18 | - uses: actions/checkout@v4 19 | 20 | - name: Build sdist 21 | run: pipx run build --sdist 22 | 23 | - uses: actions/upload-artifact@v4 24 | with: 25 | name: cibw-sdist 26 | path: dist/*.tar.gz 27 | 28 | upload_pypi: 29 | needs: [build_wheels, build_sdist] 30 | runs-on: ubuntu-latest 31 | environment: pypi 32 | permissions: 33 | id-token: write 34 | steps: 35 | - uses: actions/download-artifact@v4 36 | with: 37 | # unpacks all CIBW artifacts into dist/ 38 | pattern: cibw-* 39 | path: dist 40 | merge-multiple: true 41 | 42 | - uses: pypa/gh-action-pypi-publish@release/v1 43 | with: 44 | repository-url: https://upload.pypi.org/legacy/ 45 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | .DS_Store 3 | releases/* 4 | dist/* 5 | build/* 6 | src/system_info.h 7 | prober/foo 8 | notes.md 9 | posix_ipc.egg-info/* 10 | test.c 11 | post_dist.py 12 | -------------------------------------------------------------------------------- /INSTALL: -------------------------------------------------------------------------------- 1 | `posix_ipc` is available from PyPI: 2 | 3 | pip install posix-ipc 4 | 5 | If you have the source code, you can install `posix_ipc` with this command: 6 | 7 | python -m pip install . 8 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2008 - 2025, Philip Semanchuk 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are met: 6 | * Redistributions of source code must retain the above copyright 7 | notice, this list of conditions and the following disclaimer. 8 | * Redistributions in binary form must reproduce the above copyright 9 | notice, this list of conditions and the following disclaimer in the 10 | documentation and/or other materials provided with the distribution. 11 | * Neither the name of posix_ipc nor the names of its contributors may be 12 | used to endorse or promote products derived from this software without 13 | specific prior written permission. 14 | 15 | THIS SOFTWARE IS PROVIDED BY ITS CONTRIBUTORS ''AS IS'' AND ANY 16 | EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 17 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 18 | DISCLAIMED. IN NO EVENT SHALL Philip Semanchuk BE LIABLE FOR ANY 19 | DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 20 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 21 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 22 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 23 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 24 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 25 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include INSTALL LICENSE VERSION 2 | include README.md USAGE.md history.md 3 | include src/posix_ipc_module.c 4 | # Don't include system_info.h. It's system specific, and should be re-created 5 | # for each build except under unusual circumstances. 6 | exclude src/system_info.h 7 | graft build_support 8 | graft tests 9 | # I use recursive-include instead of graft so that I don't accidentally 10 | # include any compiled object files or executables that might be laying around 11 | # on my hard drive. 12 | recursive-include demos *.h *.c *.py *.sh *.png *.txt *.md 13 | 14 | global-exclude .DS_Store 15 | global-exclude *.py[cod] -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # POSIX IPC 2 | 3 | `posix_ipc` is a Python module (written in C) that permits creation and manipulation of POSIX inter-process semaphores, shared memory and message queues on platforms supporting the POSIX Realtime Extensions a.k.a. POSIX 1003.1b-1993. This includes nearly all Unices, and Windows + Cygwin ≥ 1.7. 4 | 5 | **For complete documentation, see [the usage notes](USAGE.md).** 6 | 7 | `posix_ipc` is compatible with all supported versions of Python 3. Older versions of `posix_ipc` may [still work under Python 2.x](USAGE.md#support-for-older-pythons). 8 | 9 | If you want to build your own copy of `posix_ipc`, see [the build notes](building.md). 10 | 11 | ## Installation 12 | 13 | `posix_ipc` is available from PyPI: 14 | 15 | pip install posix-ipc 16 | 17 | If you have the source code, you can install `posix_ipc` with this command: 18 | 19 | python -m pip install . 20 | 21 | ## Tests 22 | 23 | `posix_ipc` has a robust test suite. To run tests -- 24 | 25 | python -m unittest discover --verbose 26 | 27 | ## License 28 | 29 | `posix_ipc` is free software (free as in speech and free as in beer) released under a 3-clause BSD license. Complete licensing information is available in [the LICENSE file](LICENSE). 30 | 31 | ## Support 32 | 33 | If you have comments, questions, or ideas to share, please use the mailing list: 34 | https://groups.io/g/python-posix-ipc/ 35 | 36 | If you think you've found a bug, you can file an issue on GitHub: 37 | https://github.com/osvenskan/posix_ipc/issues 38 | 39 | Please note that as of this writing (2025), it's been seven years since anyone found a bug in the core code, so maybe ask on the mailing list first. 🙂 40 | 41 | ## Related 42 | 43 | You might also be interested in the similar System V IPC module: https://github.com/osvenskan/sysv_ipc 44 | -------------------------------------------------------------------------------- /USAGE.md: -------------------------------------------------------------------------------- 1 | # POSIX IPC for Python - Semaphores, Shared Memory and Message Queues 2 | 3 | The Python extension module `posix_ipc` gives Python access to POSIX interprocess semaphores, shared memory and message queues on systems that support the POSIX Realtime Extensions a.k.a. POSIX 1003.1b-1993. That includes most (all?) Linuxes with kernel ≥ 2.6, FreeBSD ≥ 7.2, and OpenSolaris ≥ 2008.05. This module doesn't support unnamed (anonymous) POSIX semaphores. It is released under [a BSD license](LICENSE). 4 | 5 | Mac and other Unix-y platforms (including Windows + [Cygwin 1.7](http://www.cygwin.com/)) provide partial (or partially broken) support. See [the platform notes below](#platform-notes) for more details. 6 | 7 | This extension allows Python to interact with non-Python apps via IPC. If you want IPC between Python apps, you're better off using the [`multiprocessing` module](https://docs.python.org/3/library/multiprocessing.html) or the [`multiprocessing.shared_memory module`](https://docs.python.org/3/library/multiprocessing.shared_memory.html) from Python's standard library. 8 | 9 | The [source code for `posix_ipc` is on GitHub](https://github.com/osvenskan/posix_ipc/), along with [some sample code](#sample-code). 10 | 11 | You might want to read [all of the changes in this version](history.md) and about some [known bugs](#known-bugs). 12 | 13 | You might be interested in the very similar module [`sysv_ipc` which provides Python access to IPC using System V semaphores, shared memory and message queues](https://github.com/osvenskan/sysv_ipc/). System V IPC has broader OS support, but many people find it less easy to use. 14 | 15 | # Module `posix_ipc` Documentation 16 | 17 | Jump to [semaphores](#the-semaphore-class), [shared memory](#the-sharedmemory-class), or [message queues](#the-messagequeue-class). 18 | 19 | ### Module Functions 20 | 21 | `unlink_semaphore(name)` 22 | 23 | `unlink_shared_memory(name)` 24 | 25 | `unlink_message_queue(name)` 26 | 27 | Convenience functions that unlink the IPC object described by *name*. 28 | 29 | ### Module Constants 30 | 31 | `O_CREX, O_CREAT, O_EXCL and O_TRUNC` 32 | 33 | These flags are used when creating IPC objects. All except `O_CREX` are bitwise unique and can be ORed together. `O_CREX` is shorthand for `O_CREAT | O_EXCL`. 34 | `O_TRUNC` is only useful when creating SharedMemory objects. 35 |

36 | 37 | `PAGE_SIZE` 38 | 39 | The operating system's memory page size, in bytes. It's probably a good idea to make shared memory segments some multiple of this size. 40 |

41 | 42 | `SEMAPHORE_TIMEOUT_SUPPORTED` 43 | 44 | True if the underlying OS supports `sem_timedwait()`. If False, all timeouts > 0 passed to a semaphore's `acquire()` method are treated as infinity. 45 | As far as I know, this is only False under macOS. 46 |

47 | 48 | `SEMAPHORE_VALUE_SUPPORTED` 49 | 50 | True if the underlying OS supports `sem_getvalue()`. If False, accessing the `value` attribute on a `Semaphore` instance will raise an AttributeError. 51 | As far as I know, this is only False under macOS. 52 |

53 | 54 | `SEMAPHORE_VALUE_MAX` 55 | 56 | The maximum value that can be assigned to a semaphore. 57 |

58 | 59 | `MESSAGE_QUEUES_SUPPORTED` 60 | 61 | True if the underlying OS supports message queues, False otherwise. 62 |

63 | 64 | `QUEUE_MESSAGES_MAX_DEFAULT` 65 | 66 | The default value for a message queue's `max_messages` attribute. This can be quite small under Linux (e.g. 10) but is usually LONG_MAX everywhere else. 67 |

68 | 69 | `QUEUE_MESSAGE_SIZE_MAX_DEFAULT` 70 | 71 | The default value for a message queue's `max_message_size` attribute. This is 8k (or possibly smaller under Linux). 72 |

73 | 74 | `QUEUE_PRIORITY_MAX` 75 | 76 | The maximum message queue message priority. 77 |

78 | 79 | `USER_SIGNAL_MIN, USER_SIGNAL_MAX` 80 | 81 | The constants define a range of signal values reserved for use by user applications (like yours). They're available only on systems that support the POSIX Realtime Signals Extension. Most systems do; NetBSD versions prior to 6.0 are a notable exception. 82 |

83 | 84 | `VERSION` 85 | 86 | The module version string, e.g. `'0.9.8'`. This is also available as `__version__`. 87 |

88 | 89 | ### Module Errors 90 | 91 | In addition to standard Python errors (e.g. `ValueError`), this module raises custom errors. These errors cover situations specific to IPC. 92 | 93 | `Error` 94 | 95 | The base error class for all the custom errors in this module. 96 |

97 | 98 | `SignalError` 99 | 100 | Raised when a waiting call (e.g. `sem.acquire()`) is interrupted by a signal other than KeyboardInterrupt. 101 |

102 | 103 | `PermissionsError` 104 | 105 | Indicates that you've attempted something that the permissions on the IPC object don't allow. 106 |

107 | 108 | `ExistentialError` 109 | 110 | Indicates an error related to the existence or non-existence of an IPC object. 111 |

112 | 113 | 114 | `BusyError` 115 | 116 | Raised when a call times out. 117 | 118 | ## The Semaphore Class 119 | 120 | This is a handle to a semaphore. 121 | 122 | ### Constructor 123 | 124 | `Semaphore(name, [flags = 0, [mode = 0600, [initial_value = 0]]])` 125 | 126 | Creates a new semaphore or opens an existing one. 127 | 128 | *name* must be `None` or a string. If it is `None`, the module chooses a random unused name. If it is a string, it should begin with a slash and be valid according to pathname rules on your system, e.g. `/wuthering_heights_by_semaphore` 129 | 130 | The *flags* specify whether you want to create a new semaphore or open an existing one. 131 | 132 | - With *flags* set to the default of `0`, the module attempts to open an existing semaphore and raises an error if that semaphore doesn't exist. 133 | - With *flags* set to `O_CREAT`, the module opens the semaphore if it exists (in which case mode and initial value are ignored) or creates it if it doesn't. 134 | - With *flags* set to `O_CREAT | O_EXCL` (or `O_CREX`), the module creates a new semaphore identified by *name*. If a semaphore with that name already exists, the call raises an `ExistentialError`. 135 | 136 | ### Instance Methods 137 | 138 | `acquire([timeout = None])` 139 | 140 | Waits (conditionally) until the semaphore's value is > 0 and then returns, decrementing the semaphore. 141 | 142 | The *timeout* (which can be a float) specifies how many seconds this call should wait, if at all. 143 | 144 | - A *timeout* of None (the default) implies no time limit. The call will not return until its wait condition is satisfied. 145 | - When *timeout* is 0, the call immediately raises a `BusyError` if asked to wait. Since it will return immediately if not asked to wait, this can be thought of as "non-blocking" mode. 146 | 147 | This behavior is unaffected by whether or not the platform supports `sem_timedwait()` (see below). 148 | 149 | - When the *timeout* is > 0, the call will wait no longer than *timeout* seconds before either returning (having acquired the semaphore) or raising a `BusyError`. 150 | 151 | On platforms that don't support the `sem_timedwait()` API, a *timeout* > 0 is treated as infinite. The call will not return until its wait condition is satisfied. 152 | 153 | Most platforms provide `sem_timedwait()`. macOS is a notable exception. The module's Boolean constant `SEMAPHORE_TIMEOUT_SUPPORTED` is True on platforms that support `sem_timedwait()`. 154 |

155 | 156 | `release()` 157 | 158 | Releases (increments) the semaphore. 159 |

160 | 161 | `close()` 162 | 163 | Closes the semaphore, indicating that the current *process* is done with the semaphore. The effect of subsequent use of the semaphore by the current process is undefined. Assuming it still exists, (see `unlink()`, below) the semaphore can be re-opened. 164 | 165 | You must call `close()` explicitly; it is **not** called automatically when a Semaphore object is garbage collected. 166 |

167 | 168 | `unlink()` 169 | 170 | Destroys the semaphore, with a caveat. If any processes have the semaphore open when unlink is called, the call to unlink returns immediately but destruction of the semaphore is postponed until all processes have closed the semaphore. 171 | 172 | Note, however, that once a semaphore has been unlinked, calls to `open()` with the same name should refer to a new semaphore. Sound confusing? It is, and you'd probably be wise structure your code so as to avoid this situation. 173 |

174 | 175 | ### Instance Attributes 176 | 177 | `name` **(read-only)** 178 | 179 | The name provided in the constructor. 180 |

181 | 182 | `value` **(read-only)** 183 | 184 | The integer value of the semaphore. Not available on macOS. (See [Platforms](#platform-notes)) 185 |

186 | 187 | ### Context Manager Support 188 | 189 | These semaphores support the context manager protocol so they can be used with Python's `with` statement, just like Python's `threading.Semaphore`. For instance -- 190 | 191 | ```python 192 | with posix_ipc.Semaphore(name) as sem: 193 | # Do something... 194 | ``` 195 | 196 | Entering the context acquires the semaphore, exiting the context releases the semaphore. See `demo4/child.py` for a complete example. The context manager only manages acquisition and release. If you create a new semaphore as part of executing the `with` statement, you must explicitly unlink it. 197 | 198 | ## The SharedMemory Class 199 | 200 | This is a handle to a shared memory segment. POSIX shared memory segments masquerade as files, and so the handle to a shared memory segment is just a file descriptor that can be mmapped. 201 | 202 | ### Constructor 203 | 204 | `SharedMemory(name, [flags = 0, [mode = 0600, [size = 0, [read_only = false]]]])` 205 | 206 | Creates a new shared memory segment or opens an existing one. 207 | 208 | *name* must be `None` or a string. If it is `None`, the module chooses a random unused name. If it is a string, it should begin with a slash and be valid according to pathname rules on your system, e.g. `/four_yorkshiremen_sharing_memories` 209 | 210 | On some systems you need to have write access to the path. 211 | 212 | The *flags* specify whether you want to create a new shared memory segment or open an existing one. 213 | 214 | - With *flags* set to the default of `0`, the module attempts to open an existing segment and raises an error if that segment doesn't exist. 215 | - With *flags* set to `O_CREAT`, the module opens the segment if it exists or creates it if it doesn't. 216 | - With *flags* set to `O_CREAT | O_EXCL` (or `O_CREX`), the module creates a new shared memory segment identified by *name*. If a segment with that name already exists, the call raises an `ExistentialError`. 217 | 218 | If you pass a non-zero size, the segment will be (re)sized accordingly, regardless of whether or not it's a new or existing segment. Prior to version 1.0.4, this documentation incorrectly stated that size was ignored if the segment already existed. 219 | 220 | To (re)size the segment, `posix_ipc` calls `ftruncate()`. The same function is available to Python via [`os.ftruncate()`](https://docs.python.org/3/library/os.html#os.ftruncate). If you prefer to handle segment (re)sizing yourself, leave the `SharedMemory` parameter `size` at its default of `0` and call `os.ftruncate()` when and how you like. 221 | 222 | Note that under macOS (up to and including 10.12 at least), you can only call `ftruncate()` once on a segment during its lifetime. This is a limitation of macOS, not `posix_ipc`. 223 | 224 | When opening an existing shared memory segment, one can also specify the flag `O_TRUNC` to truncate the shared memory to zero bytes. macOS does not support `O_TRUNC`. 225 | 226 | ### Instance Methods 227 | 228 | `close_fd()` 229 | 230 | Closes the file descriptor associated with this SharedMemory object. Calling `close_fd()` is the same as calling [`os.close()`](hhttps://docs.python.org/3/library/os.html#os.close) on a SharedMemory object's `fd` attribute. 231 | 232 | You must call `close_fd()` or `os.close()` explicitly; the file descriptor is **not** closed automatically when a SharedMemory object is garbage collected. 233 | 234 | Closing the file descriptor has no effect on any `mmap` objects that were created from it. See the demo for an example. 235 |

236 | 237 | `unlink()` 238 | 239 | Marks the shared memory for destruction once all processes have unmapped it. 240 | 241 | [The POSIX specification for `shm_unlink()`](http://www.opengroup.org/onlinepubs/009695399/functions/shm_unlink.html) says, "Even if the object continues to exist after the last shm_unlink(), reuse of the name shall subsequently cause shm_open() to behave as if no shared memory object of this name exists (that is, shm_open() will fail if O_CREAT is not set, or will create a new shared memory object if O_CREAT is set)." 242 | 243 | I'll bet a virtual cup of coffee that this tricky part of the standard is not well or consistently implemented in every OS. Caveat emptor. 244 | 245 | ### Instance Attributes 246 | 247 | `name` **(read-only)** 248 | 249 | The name provided in the constructor. 250 |

251 | 252 | `fd` **(read-only)** 253 | 254 | The file descriptor that represents the memory segment. 255 |

256 | 257 | `size` **(read-only)** 258 | 259 | The size (in bytes) of the shared memory segment. 260 | 261 | ## The MessageQueue Class 262 | 263 | This is a handle to a message queue. 264 | 265 | ### Constructor 266 | 267 | `MessageQueue(name, [flags = 0, [mode = 0600, [max_messages = QUEUE_MESSAGES_MAX_DEFAULT, [max_message_size = QUEUE_MESSAGE_SIZE_MAX_DEFAULT, [read = True, [write = True]]]]]])` 268 | 269 | Creates a new message queue or opens an existing one.*name* must be `None` or a string. If it is `None`, the module chooses a random unused name. If it is a string, it should begin with a slash and be valid according to pathname rules on your system, e.g. `/my_message_queue` 270 | On some systems you need to have write access to the path. 271 | The *flags* specify whether you want to create a new queue or open an existing one. 272 | 273 | - With *flags* set to the default of `0`, the module attempts to open an existing queue and raises an error if that queue doesn't exist. 274 | - With *flags* set to `O_CREAT`, the module opens the queue if it exists (in which case *size* and *mode* are ignored) or creates it if it doesn't. 275 | - With *flags* set to `O_CREAT | O_EXCL` (or `O_CREX`), the module creates a new message queue identified by *name*. If a queue with that name already exists, the call raises an `ExistentialError`. 276 | 277 | *Max_messages* defines how many messages can be in the queue at one time. When the queue is full, calls to `.send()` will wait. 278 | *Max_message_size* defines the maximum size (in bytes) of a message. 279 | *Read* and *write* default to True. If *read/write* is False, calling `.receive()/.send()` on this object is not permitted. This doesn't affect other handles to the same queue. 280 | 281 | ### Instance Methods 282 | 283 | `send(message, [timeout = None, [priority = 0]])` 284 | 285 | Sends a message via the queue.The *message* string can contain embedded NULLs (ASCII `0x00`). Under Python 3, the message can also be a bytes object. 286 | The *timeout* (which can be a float) specifies how many seconds this call should wait if the queue is full. Timeouts are irrelevant when the `block` flag is False. 287 | 288 | - A *timeout* of None (the default) implies no time limit. The call will not return until the message is sent. 289 | - When *timeout* is 0, the call immediately raises a `BusyError` if asked to wait. 290 | - When the *timeout* is > 0, the call will wait no longer than *timeout* seconds before either returning (having sent the message) or raising a `BusyError`. 291 | 292 | The *priority* allows you to order messages in the queue. The highest priority message is received first. By default, messages are sent at the lowest priority (0). 293 |

294 | 295 | `receive([timeout = None])` 296 | 297 | Receives a message from the queue, returning a tuple of `(message, priority)`. Messages are received in the order of highest priority to lowest, and in FIFO order for messages of equal priority. Under Python 3, the returned message is a bytes object. 298 | If the queue is empty, the call will not return immediately. The optional *timeout* parameter controls the wait just as for the function `send()`. It defaults to None. 299 |

300 | 301 | `request_notification([notification = None])` 302 | 303 | Depending on the parameter, requests or cancels notification from the operating system when the queue changes from empty to non-empty. 304 | 305 | - When *notification* is not provided or `None`, any existing notification request is cancelled. 306 | - When *notification* is an integer, notification will be sent as a signal of this value that can be caught using a signal handler installed with `signal.signal()`. 307 | - When *notification* is a tuple of `(function, param)`, notification will be sent by invoking *`function(param)`* in a new thread. 308 | 309 | Message queues accept only one notification request at a time. If another process has already requested notifications from this queue, this call will fail with a `BusyError`. 310 | The operating system delivers (at most) one notification per request. If you want subsequent notifications, you must request them by calling `request_notification()` again. 311 |

312 | 313 | `close()` 314 | 315 | Closes this reference to the queue.You must call `close()` explicitly; it is **not** called automatically when a MessageQueue object is garbage collected. 316 |

317 | 318 | `unlink()` 319 | 320 | Requests destruction of the queue. Although the call returns immediately, actual destruction of the queue is postponed until all references to it are closed. 321 | 322 | ### Instance Attributes 323 | 324 | `name` **(read-only)** 325 | 326 | The name provided in the constructor. 327 |

328 | 329 | `mqd` **(read-only)** 330 | 331 | The message queue descriptor that represents the queue. 332 |

333 | 334 | `block` 335 | 336 | When True (the default), calls to `.send()` and `.receive()` may wait (block) if they cannot immediately satisfy the send/receive request. When `block` is False, the module will raise `BusyError` instead of waiting. 337 |

338 | 339 | `max_messages` **(read-only)** 340 | 341 | The maximum number of messages the queue can hold. 342 |

343 | 344 | `max_message_size` **(read-only)** 345 | 346 | The maximum message size (in bytes). 347 |

348 | 349 | `current_messages` **(read-only)** 350 | 351 | The number of messages currently in the queue. 352 | 353 | ## Usage Tips 354 | 355 | ### Tests 356 | 357 | This module comes with fairly complete unit tests in the `tests` directory. To run them, install `posix_ipc` and then run this command from the same directory as `setup.py`: 358 | 359 | ```bash 360 | python -m unittest discover 361 | ``` 362 | 363 | ### Sample Code 364 | 365 | This module comes with five sets of demonstration code, all [in the `demos` directory](https://github.com/osvenskan/posix_ipc/tree/develop/demos). The first (in the directory `demo1`) shows how to use shared memory and semaphores. The second (in the directory `demo2`) shows how to use message queues. The third (`demo3`) shows how to use message queue notifications. The fourth (`demo4`) shows how to use a semaphore in a context manager. The fifth (`demo5`) demonstrates use of message queues in combination with Python's `selectors` module. 366 | 367 | ### Nobody Likes a Mr. Messy 368 | 369 | IPC objects are a little different from most Python objects and therefore require a little more care on the part of the programmer. When a program creates a IPC object, it creates something that resides *outside of its own process*, just like a file on a hard drive. It won't go away when your process ends unless you explicitly remove it. And since many operating systems don't even give you a way to enumerate existing POSIX IPC entities, it might be hard to figure out what you're leaving behind. 370 | 371 | In short, remember to clean up after yourself. 372 | 373 | ### Semaphores and References 374 | 375 | I know it's *verboten* to talk about pointers in Python, but I'm going to do it anyway. 376 | 377 | Each Semaphore object created by this module contains a C pointer to the IPC object created by the system. When you call `sem.close()`, the object's internal pointer is set to `NULL`. This leaves the Python object in a not-quite-useless state. You can still call `sem.unlink()` or print `sem.name`, but calls to `sem.aquire()` or `sem.release()` will raise an `ExistentialError`. 378 | 379 | If you know you're not going to use a Semaphore object after calling `sem.close()` or `sem.unlink()`, you could you set your semaphore variable to the return from the function (which is always `None`) like so: 380 | 381 | ``` 382 | my_sem = my_sem.close() 383 | ``` 384 | 385 | That will ensure you don't have any nearly useless objects laying around that you might use by accident. 386 | 387 | This doesn't apply to shared memory and message queues because they're referenced at the C level by a file descriptor rather than a pointer. 388 | 389 | ### Permissions 390 | 391 | It appears that the read and write mode bits on IPC objects are ignored by the operating system. For instance, on macOS, OpenSolaris and Linux one can write to semaphores and message queues with a mode of `0400`. 392 | 393 | ### Message Queues 394 | 395 | When creating a new message queue, you specify a maximum message size which defaults to `QUEUE_MESSAGE_SIZE_MAX_DEFAULT` (currently 8192 bytes). You can create a queue with a larger value, but be aware that `posix_ipc` allocates a buffer the size of the maximum message size every time `receive()` is called. 396 | 397 | ### Consult Your Local `man` Pages 398 | 399 | The posix_ipc module is just a wrapper around your system's API. If your system's implementation has quirks, the `man` pages for `sem_open, sem_post, sem_wait, sem_close, sem_unlink, shm_open, shm_unlink, mq_open, mq_send mq_receive, mq_getattr, mq_close, mq_unlink` 400 | and `mq_notify` will probably cover them. 401 | 402 | ### Last But Not Least 403 | 404 | For Pythonistas, [a meditation on the inaccuracy of shared memories](https://www.youtube.com/watch?v=VKHFZBUTA4k) 405 | 406 | ## Known Bugs 407 | 408 | I don't know of any bugs in this code, but FreeBSD users should check the platform notes. 409 | 410 | ## Support for Older Pythons 411 | 412 | [Version 1.0.5 of `posix_ipc`](https://pypi.org/project/posix-ipc/1.0.5/) was the last to support both Python 2.7 and Python 3.x. No changes (neither fixes nor features) will be backported to 1.0.5. 413 | 414 | If you need to support Python < 2.7, try [posix_ipc version 0.9.9](https://pypi.org/project/posix-ipc/0.9.9/). 415 | 416 | # Platform Notes 417 | 418 | This module is just a wrapper around the operating system's functions, so if the operating system doesn't provide a function, this module can't either. The POSIX Realtime Extensions (POSIX 1003.1b-1993) are, as the name implies, an extension to POSIX and so a platform can claim "*POSIX conformance*" and still not support any or all of the IPC functions. 419 | 420 | **Linux with kernel ≥ 2.6** 421 | 422 | All features supported. 423 | 424 | **OpenSolaris ≥ 2008.05** 425 | 426 | All features supported. 427 | 428 | **FreeBSD ≥ 7.2** 429 | 430 | All features supported.Under 7.2, `posix_ipc`'s demos fail unless they're run as root. It's a simple permissions problem. Prefix the IPC object names with `/tmp` in `params.txt` and the problem goes away. I didn't see this behavior under FreeBSD 8.0, so it must have been fixed at some point. 431 | If you don't have the `sem` and `mqueuefs` kernel modules loaded, you'll get a message like this (or something similarly discouraging) when you try to create a semaphore or message queue:`Bad system call: 12 (core dumped)` 432 | Type `kldstat` to list loaded modules, and `kldload sem` or `kldload mqueuefs` if you need to load either of these. BTW, [mqueuefs](http://www.freebsd.org/cgi/man.cgi?query=mqueuefs&apropos=0&sektion=5&manpath=FreeBSD+8.0-stable&format=html) has some cool features. 433 | Prior to 7.2, FreeBSD POSIX semaphore support was [broken](http://www.freebsd.org/cgi/query-pr.cgi?pr=127545). 434 | Under FreeBSD 11.1, I have seen segfaults during the message queue threaded notification rearm test. I don't know if `posix_ipc` or FreeBSD (or both!) are culprits. 435 | 436 | **macOS/OS X (up to and including 15.4)** 437 | 438 | macOS' implementation of POSIX IPC has some significant holes. Message queues are not supported at all. Also, `sem_getvalue()` and `sem_timedwait()` are not supported. 439 | 440 | **Windows + Cygwin 1.7** 441 | 442 | [Cygwin](http://www.cygwin.com/) is a Linux-like environment for Windows. 443 | Versions of Cygwin prior to 1.7 didn't support POSIX IPC. Under Cygwin 1.7 beta 62 (released in early October 2009), `posix_ipc` compiles and runs both demos. 444 | -------------------------------------------------------------------------------- /VERSION: -------------------------------------------------------------------------------- 1 | 1.2.0 -------------------------------------------------------------------------------- /build_support/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/osvenskan/posix_ipc/e0ee0128534cfd774ff3091ad7f068264661d896/build_support/__init__.py -------------------------------------------------------------------------------- /build_support/discover_system_info.py: -------------------------------------------------------------------------------- 1 | import subprocess 2 | import platform 3 | import os 4 | import sys 5 | 6 | # Set these to None for compile/link debugging or subprocess.PIPE to silence 7 | # compiler warnings and errors. 8 | STDOUT = subprocess.PIPE 9 | STDERR = subprocess.PIPE 10 | # STDOUT = None 11 | # STDERR = None 12 | 13 | # This is the max length that I want a printed line to be. 14 | MAX_LINE_LENGTH = 78 15 | 16 | 17 | def line_wrap_paragraph(s): 18 | # Format s with terminal-friendly line wraps. 19 | done = False 20 | beginning = 0 21 | end = MAX_LINE_LENGTH - 1 22 | lines = [] 23 | while not done: 24 | if end >= len(s): 25 | done = True 26 | lines.append(s[beginning:]) 27 | else: 28 | last_space = s[beginning:end].rfind(' ') 29 | 30 | lines.append(s[beginning:beginning + last_space]) 31 | beginning += (last_space + 1) 32 | end = beginning + MAX_LINE_LENGTH - 1 33 | 34 | return lines 35 | 36 | 37 | def print_bad_news(value_name, default): 38 | s = "Setup can't determine %s on your system, so it will default to %s which " \ 39 | "may not be correct." % (value_name, default) 40 | plea = "Please report this message and your operating system info to the package " \ 41 | "maintainer listed in the README file." 42 | 43 | lines = line_wrap_paragraph(s) + [''] + line_wrap_paragraph(plea) 44 | 45 | border = '*' * MAX_LINE_LENGTH 46 | 47 | s = border + "\n* " + ('\n* '.join(lines)) + '\n' + border 48 | 49 | print(s) 50 | 51 | 52 | def does_build_succeed(filename, linker_options=""): 53 | # Utility function that returns True if the file compiles and links 54 | # successfully, False otherwise. 55 | # Two things to note here -- 56 | # - If there's a linker option like -lrt, it needs to come *after* 57 | # the specification of the C file or linking will fail on Ubuntu 11.10 58 | # (maybe because of the gcc version?) 59 | # - Some versions of Linux place the sem_xxx() functions in libpthread. 60 | # Rather than testing whether or not it's needed, I just specify it 61 | # everywhere since it's harmless to specify it when it's not needed. 62 | cc = os.getenv("CC", "cc") 63 | cmd = "%s -Wall -o ./build_support/src/foo ./build_support/src/%s %s -lpthread" % (cc, filename, linker_options) 64 | 65 | p = subprocess.Popen(cmd, shell=True, stdout=STDOUT, stderr=STDERR) 66 | 67 | # p.wait() returns the process' return code, so 0 implies that 68 | # the compile & link succeeded. 69 | return not bool(p.wait()) 70 | 71 | 72 | def compile_and_run(filename, linker_options=""): 73 | # Utility function that returns the stdout output from running the 74 | # compiled source file; None if the compile fails. 75 | cc = os.getenv("CC", "cc") 76 | cmd = "%s -Wall -o ./build_support/src/foo %s ./build_support/src/%s" % (cc, linker_options, filename) 77 | 78 | p = subprocess.Popen(cmd, shell=True, stdout=STDOUT, stderr=STDERR) 79 | 80 | if p.wait(): 81 | # uh-oh, compile failed 82 | return None 83 | 84 | try: 85 | s = subprocess.Popen(["./build_support/src/foo"], 86 | stdout=subprocess.PIPE).communicate()[0] 87 | return s.strip().decode() 88 | except Exception: 89 | # execution resulted in an error 90 | return None 91 | 92 | 93 | def get_sysctl_value(name): 94 | """Given a sysctl name (e.g. 'kern.mqueue.maxmsg'), returns sysctl's value 95 | for that variable or None if the sysctl call fails (unknown name, not 96 | a BSD-ish system, etc.) 97 | 98 | Only makes sense on systems that implement sysctl (BSD derivatives). 99 | """ 100 | s = None 101 | try: 102 | # I redirect stderr to /dev/null because if sysctl is availble but 103 | # doesn't know about the particular item I'm querying, it will 104 | # kvetch with a message like 'second level name mqueue in 105 | # kern.mqueue.maxmsg is invalid'. This always happens under OS X 106 | # (which doesn't have any kern.mqueue values) and under FreeBSD when 107 | # the mqueuefs kernel module isn't loaded. 108 | s = subprocess.Popen(["sysctl", "-n", name], 109 | stdout=subprocess.PIPE, 110 | stderr=open(os.devnull, 'rw')).communicate()[0] 111 | s = s.strip().decode() 112 | except: 113 | pass 114 | 115 | return s 116 | 117 | 118 | def sniff_realtime_lib(): 119 | rc = None 120 | filename = "sniff_realtime_lib.c" 121 | 122 | if does_build_succeed(filename): 123 | # Realtime libs not needed 124 | rc = False 125 | else: 126 | # cc failed when not linked to realtime libs; let's try again 127 | # with the realtime libs involved and see if things go better. 128 | if does_build_succeed(filename, "-lrt"): 129 | # Realtime libs are needed 130 | rc = True 131 | 132 | if rc is None: 133 | # Unable to determine whether or not I needed the realtime libs. 134 | # That's bad! Print a warning, set the return code to False 135 | # and hope for the best. 136 | rc = False 137 | print_bad_news("if it needs to link to the realtime libraries", "'no'") 138 | 139 | return rc 140 | 141 | 142 | def sniff_sem_getvalue(linker_options): 143 | return does_build_succeed("sniff_sem_getvalue.c", linker_options) 144 | 145 | 146 | def sniff_sem_timedwait(linker_options): 147 | return does_build_succeed("sniff_sem_timedwait.c", linker_options) 148 | 149 | 150 | def sniff_sem_value_max(): 151 | # default is to return None which means that it is #defined in a standard 152 | # header file and doesn't need to be added to my custom header file. 153 | sem_value_max = None 154 | 155 | if not does_build_succeed("sniff_sem_value_max.c"): 156 | # OpenSolaris 2008.05 doesn't #define SEM_VALUE_MAX. (This may 157 | # be true elsewhere too.) Ask sysconf() instead if it exists. 158 | # Note that sys.sysconf_names doesn't exist under Cygwin. 159 | if hasattr(os, "sysconf_names") and \ 160 | ("SC_SEM_VALUE_MAX" in os.sysconf_names): 161 | sem_value_max = os.sysconf("SC_SEM_VALUE_MAX") 162 | else: 163 | # This value of last resort should be #defined everywhere. What 164 | # could possibly go wrong? 165 | sem_value_max = "_POSIX_SEM_VALUE_MAX" 166 | 167 | return sem_value_max 168 | 169 | 170 | def sniff_page_size(): 171 | # 4096 is a common page size on x86. As the world moves increasingly to ARM architectures, 172 | # this might not be a good default anymore, but the default value isn't really supposed to 173 | # be used except when all else fails (and in that case the user gets a warning). 174 | DEFAULT_PAGE_SIZE = 4096 175 | 176 | page_size = None 177 | 178 | # When cross compiling under cibuildwheel, I need to rely on their custom env var to set the 179 | # page size correctly. See https://github.com/osvenskan/posix_ipc/issues/58 180 | if 'arm' in os.getenv('_PYTHON_HOST_PLATFORM', ''): 181 | page_size = 16384 182 | 183 | if not page_size: 184 | # Maybe I can find page size in os.sysconf(). If so, that saves a compilation step. 185 | if 'SC_PAGESIZE' in os.sysconf_names: 186 | page_size = os.sysconf('SC_PAGESIZE') 187 | 188 | if not page_size: 189 | # OK, I have to do it the hard way. I don't need to worry about linker options here 190 | # because I'm not calling any functions, just getting the value of a #define. 191 | page_size = compile_and_run("sniff_page_size.c") 192 | 193 | if not page_size: 194 | page_size = DEFAULT_PAGE_SIZE 195 | print_bad_news("the value of PAGE_SIZE", page_size) 196 | 197 | return page_size 198 | 199 | 200 | def sniff_mq_existence(linker_options): 201 | return does_build_succeed("sniff_mq_existence.c", linker_options) 202 | 203 | 204 | def sniff_mq_prio_max(): 205 | # MQ_PRIO_MAX is #defined in limits.h on all of the systems that I 206 | # checked that support message queues at all. (I checked 2 Linux boxes, 207 | # OpenSolaris and FreeBSD 8.0.) 208 | 209 | # 32 = minimum allowable max priority per POSIX; systems are permitted 210 | # to define a larger value. 211 | # ref: http://www.opengroup.org/onlinepubs/009695399/basedefs/limits.h.html 212 | DEFAULT_PRIORITY_MAX = 32 213 | 214 | max_priority = None 215 | # OS X up to and including 10.8 doesn't support POSIX messages queues and 216 | # doesn't define MQ_PRIO_MAX. Maybe this aggravation will cease in 10.9? 217 | if does_build_succeed("sniff_mq_prio_max.c"): 218 | max_priority = compile_and_run("sniff_mq_prio_max.c") 219 | 220 | if max_priority: 221 | try: 222 | max_priority = int(max_priority) 223 | except ValueError: 224 | max_priority = None 225 | 226 | if max_priority is None: 227 | # Looking for a #define didn't work; ask sysconf() instead. 228 | # Note that sys.sysconf_names doesn't exist under Cygwin. 229 | if hasattr(os, "sysconf_names") and \ 230 | ("SC_MQ_PRIO_MAX" in os.sysconf_names): 231 | max_priority = os.sysconf("SC_MQ_PRIO_MAX") 232 | else: 233 | max_priority = DEFAULT_PRIORITY_MAX 234 | print_bad_news("the value of PRIORITY_MAX", max_priority) 235 | 236 | # Under OS X, os.sysconf("SC_MQ_PRIO_MAX") returns -1. 237 | # This is still true in April 2025 under MacOS 15.3. 238 | if max_priority < 0: 239 | max_priority = DEFAULT_PRIORITY_MAX 240 | 241 | # Adjust for the fact that these are 0-based values; i.e. permitted 242 | # priorities range from 0 - (MQ_PRIO_MAX - 1). So why not just make 243 | # the #define one smaller? Because this one goes up to eleven... 244 | max_priority -= 1 245 | 246 | # priority is an unsigned int 247 | return str(max_priority).strip() + "U" 248 | 249 | 250 | def sniff_mq_max_messages(): 251 | # This value is not defined by POSIX. 252 | 253 | # On most systems I've tested, msg Qs are implemented via mmap-ed files 254 | # or a similar interface, so the only theoretical limits are imposed by the 255 | # file system. In practice, Linux and *BSD impose some fairly tight 256 | # limits. 257 | 258 | # On Linux it's available in a /proc file and often defaults to the wimpy 259 | # value of 10. 260 | 261 | # On FreeBSD (and other BSDs, I assume), it's available via sysctl as 262 | # kern.mqueue.maxmsg. On my FreeBSD 9.1 test system, it defaults to 100. 263 | 264 | # mqueue.h defines mq_attr.mq_maxmsg as a C long, so that's 265 | # a practical limit for this value. 266 | 267 | # ref: http://linux.die.net/man/7/mq_overview 268 | # ref: http://www.freebsd.org/cgi/man.cgi?query=mqueuefs&sektion=5&manpath=FreeBSD+7.0-RELEASE 269 | # http://fxr.watson.org/fxr/source/kern/uipc_mqueue.c?v=FREEBSD91#L195 270 | # ref: http://groups.google.com/group/comp.unix.solaris/browse_thread/thread/aa223fc7c91f8c38 271 | # ref: http://cygwin.com/cgi-bin/cvsweb.cgi/src/winsup/cygwin/posix_ipc.cc?cvsroot=src 272 | # ref: http://cygwin.com/cgi-bin/cvsweb.cgi/src/winsup/cygwin/include/mqueue.h?cvsroot=src 273 | mq_max_messages = None 274 | 275 | # Try to get the value from where Linux stores it. 276 | try: 277 | mq_max_messages = int(open("/proc/sys/fs/mqueue/msg_max").read()) 278 | except: 279 | # Oh well. 280 | pass 281 | 282 | if not mq_max_messages: 283 | # Maybe we're on BSD. 284 | mq_max_messages = get_sysctl_value('kern.mqueue.maxmsg') 285 | if mq_max_messages: 286 | mq_max_messages = int(mq_max_messages) 287 | 288 | if not mq_max_messages: 289 | # We're on a non-Linux, non-BSD system, or OS X, or BSD with 290 | # the mqueuefs kernel module not loaded (which it's not, by default, 291 | # under FreeBSD 8.x and 9.x which are the only systems I've tested). 292 | # 293 | # If we're on FreeBSD and mqueuefs isn't loaded when this code runs, 294 | # sysctl won't be able to provide mq_max_messages to me. (I assume other 295 | # BSDs behave the same.) If I use too large of a default, then every 296 | # attempt to create a message queue via posix_ipc will fail with 297 | # "ValueError: Invalid parameter(s)" unless the user explicitly sets 298 | # the max_messages param. 299 | if platform.system().endswith("BSD"): 300 | # 100 is the value I see under FreeBSD 9.2. I hope this works 301 | # elsewhere! 302 | mq_max_messages = 100 303 | else: 304 | # We're on a non-Linux, non-BSD system. I take a wild guess at an 305 | # appropriate value. The max possible is > 2 billion, but the 306 | # values used by Linux and FreeBSD suggest that a smaller default 307 | # is wiser. 308 | mq_max_messages = 1024 309 | 310 | return mq_max_messages 311 | 312 | 313 | def sniff_mq_max_message_size_default(): 314 | # The max message size is not defined by POSIX. 315 | 316 | # On most systems I've tested, msg Qs are implemented via mmap-ed files 317 | # or a similar interface, so the only theoretical limits are imposed by 318 | # the file system. In practice, Linux and *BSD impose some tighter limits. 319 | 320 | # On Linux, max message size is available in a /proc file and often 321 | # defaults to the value of 8192. 322 | 323 | # On FreeBSD (and other BSDs, I assume), it's available via sysctl as 324 | # kern.mqueue.maxmsgsize. On my FreeBSD 9.1 test system, it defaults to 325 | # 16384. 326 | 327 | # mqueue.h defines mq_attr.mq_msgsize as a C long, so that's 328 | # a practical limit for this value. 329 | 330 | # Further complicating things is the fact that the module has to allocate 331 | # a buffer the size of the queue's max message every time receive() is 332 | # called, so it would be a bad idea to set this default to the max. 333 | # I set it to 8192 -- not too small, not too big. I only set it smaller 334 | # if I'm on a system that tells me I must do so. 335 | DEFAULT = 8192 336 | mq_max_message_size_default = 0 337 | 338 | # Try to get the value from where Linux stores it. 339 | try: 340 | mq_max_message_size_default = \ 341 | int(open("/proc/sys/fs/mqueue/msgsize_max").read()) 342 | except: 343 | # oh well 344 | pass 345 | 346 | if not mq_max_message_size_default: 347 | # Maybe we're on BSD. 348 | mq_max_message_size_default = get_sysctl_value('kern.mqueue.maxmsgsize') 349 | if mq_max_message_size_default: 350 | mq_max_message_size_default = int(mq_max_message_size_default) 351 | 352 | if not mq_max_message_size_default: 353 | mq_max_message_size_default = DEFAULT 354 | 355 | return mq_max_message_size_default 356 | 357 | 358 | def discover(): 359 | linker_options = "" 360 | d = {} 361 | 362 | f = open("VERSION") 363 | d["POSIX_IPC_VERSION"] = '"%s"' % f.read().strip() 364 | f.close() 365 | 366 | # Sniffing of the realtime libs has to go early in the list so as 367 | # to provide correct linker options to the rest of the tests. 368 | if "Darwin" in platform.uname(): 369 | # I skip the test under Darwin/OS X for two reasons. First, I know 370 | # it isn't needed there. Second, I can't even compile the test for 371 | # the realtime lib because it references mq_unlink() which OS X 372 | # doesn't support. Unfortunately sniff_realtime_lib.c *must* 373 | # reference mq_unlink() or some other mq_xxx() function because 374 | # it is only the message queues that need the realtime libs under 375 | # FreeBSD. 376 | realtime_lib_is_needed = False 377 | else: 378 | # Some platforms (e.g. Linux & OpenSuse) require linking to librt 379 | realtime_lib_is_needed = sniff_realtime_lib() 380 | 381 | if realtime_lib_is_needed: 382 | d["REALTIME_LIB_IS_NEEDED"] = "" 383 | linker_options = " -lrt " 384 | 385 | d["PAGE_SIZE"] = sniff_page_size() 386 | 387 | if sniff_sem_getvalue(linker_options): 388 | d["SEM_GETVALUE_EXISTS"] = "" 389 | 390 | if ("SEM_GETVALUE_EXISTS" in d) and ("Darwin" in platform.uname()): 391 | # sem_getvalue() isn't available on OS X. The function exists but 392 | # always returns -1 (under OS X 10.9) or ENOSYS ("Function not 393 | # implemented") under some earlier version(s). 394 | del d["SEM_GETVALUE_EXISTS"] 395 | 396 | if sniff_sem_timedwait(linker_options): 397 | d["SEM_TIMEDWAIT_EXISTS"] = "" 398 | 399 | d["SEM_VALUE_MAX"] = sniff_sem_value_max() 400 | # A return of None means that I don't need to #define this myself. 401 | if d["SEM_VALUE_MAX"] is None: 402 | del d["SEM_VALUE_MAX"] 403 | 404 | if sniff_mq_existence(linker_options): 405 | d["MESSAGE_QUEUE_SUPPORT_EXISTS"] = "" 406 | 407 | d["QUEUE_MESSAGES_MAX_DEFAULT"] = sniff_mq_max_messages() 408 | d["QUEUE_MESSAGE_SIZE_MAX_DEFAULT"] = sniff_mq_max_message_size_default() 409 | d["QUEUE_PRIORITY_MAX"] = sniff_mq_prio_max() 410 | 411 | msg = """/* 412 | This header file was generated when you ran setup. Once created, the setup 413 | process won't overwrite it, so you can adjust the values by hand and 414 | recompile if you need to. 415 | 416 | On your platform, this file may contain only this comment -- that's OK! 417 | 418 | To enable lots of debug output, add this line and re-run setup.py: 419 | #define POSIX_IPC_DEBUG 420 | 421 | To recreate this file, just delete it and re-run setup.py. 422 | */ 423 | 424 | """ 425 | filename = "./src/system_info.h" 426 | if not os.path.exists(filename): 427 | lines = ["#define %s\t\t%s" % (key, d[key]) for key in d if key != "PAGE_SIZE"] 428 | 429 | # PAGE_SIZE gets some special treatment. It's defined in header files 430 | # on some systems in which case I might get a redefinition error in 431 | # my header file, so I wrap it in #ifndef/#endif. 432 | 433 | lines.append("#ifndef PAGE_SIZE") 434 | lines.append("#define PAGE_SIZE\t\t%s" % d["PAGE_SIZE"]) 435 | lines.append("#endif") 436 | 437 | # A trailing '\n' keeps compilers happy... 438 | open(filename, "w").write(msg + '\n'.join(lines) + '\n') 439 | 440 | return d 441 | 442 | 443 | if __name__ == "__main__": 444 | print(discover()) 445 | -------------------------------------------------------------------------------- /build_support/src/sniff_mq_existence.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | int main(void) { 4 | mq_unlink(""); 5 | 6 | return 0; 7 | } 8 | 9 | -------------------------------------------------------------------------------- /build_support/src/sniff_mq_prio_max.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | int main(void) { 5 | printf("%ld\n", (long)MQ_PRIO_MAX); 6 | 7 | return 0; 8 | } 9 | 10 | -------------------------------------------------------------------------------- /build_support/src/sniff_page_size.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | // Code for determining page size swiped from Python's mmapmodule.c 4 | #if defined(HAVE_SYSCONF) && defined(_SC_PAGESIZE) 5 | static int 6 | my_getpagesize(void) 7 | { 8 | return sysconf(_SC_PAGESIZE); 9 | } 10 | #else 11 | #include 12 | #define my_getpagesize getpagesize 13 | #endif 14 | 15 | int main(void) { 16 | printf("%d\n", my_getpagesize()); 17 | 18 | return 0; 19 | } 20 | -------------------------------------------------------------------------------- /build_support/src/sniff_realtime_lib.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | int main(void) { 4 | /* Under FreeBSD and OpenSuse, linking to the realtime lib is required, 5 | but only for mq_xxx() functions so checking for sem_xxx() or shm_xxx() 6 | here is not be a sufficient test. 7 | */ 8 | mq_unlink(""); 9 | 10 | return 0; 11 | } 12 | -------------------------------------------------------------------------------- /build_support/src/sniff_sem_getvalue.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | int main(void) { 5 | sem_getvalue(NULL, NULL); 6 | return 0; 7 | } 8 | 9 | -------------------------------------------------------------------------------- /build_support/src/sniff_sem_timedwait.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | int main(void) { 5 | sem_timedwait(NULL, NULL); 6 | return 0; 7 | } 8 | 9 | -------------------------------------------------------------------------------- /build_support/src/sniff_sem_value_max.c: -------------------------------------------------------------------------------- 1 | // The location of this constant varies. 2 | 3 | #include 4 | #include 5 | 6 | int main(void) { return SEM_VALUE_MAX; } 7 | 8 | -------------------------------------------------------------------------------- /building.md: -------------------------------------------------------------------------------- 1 | # Building/Compiling POSIX IPC 2 | 3 | You can build `posix_ipc` with a normal build command like `python -m build`. This document describes an unusual step that happen at build time. 4 | 5 | ## System Information Discovery 6 | 7 | `posix_ipc` needs to know various IPC-related facts about its host system. For instance, some operating systems don't offer a timed wait function for semaphores. This module wants to make that functionality available when it's present, and also needs to know when it's not present and therefore can't be called. 8 | 9 | This kind of information needs to be known before `posix_ipc` is compiled. To get that information, `build_support/discover_system_info.py` runs when `setup.py` is invoked. Here's some information about that script. 10 | 11 | ## The Script 12 | 13 | The best documentation for the script is currently the script itself. I hope to provide more formal documentation in a future release. Please don't laugh at the code too much. It's been adjusted over the years, but the core of it was written in 2008 when both Python and I had different standards. In `posix_ipc` releases prior to 1.2, this script was called `prober.py`. 14 | 15 | The script writes a C header file which is described below. 16 | 17 | ## The Header File 18 | 19 | The goal of `discover_system_info.py` is to write `src/system_info.h`. (In `posix_ipc` releases prior to 1.2, this file was called `probe_results.h`.) This header file isn't part of the source distribution, nor should it be. It contains values that are specific to the system on which `posix_ipc` is built. 20 | 21 | If the file exists when `discover_system_info.py` runs, it will not be overwritten. This allows developers to create their own `system_info.h` (to enable debugging messages from `posix_ipc`, for instance.) 22 | 23 | It's critical to understand that the values in the header file _describe_ your system to `posix_ipc`. They don't change the behavior of your operating system. For instance, if you decide to change `SEM_VALUE_MAX` in `system_info.h` to a larger number, that won't actually increase the maximum valid value for a semaphore on your system. It will only misinform `posix_ipc` about what your system accepts, and a misinformed `posix_ipc` will probably behave badly. 24 | 25 | ### Header File Values 26 | 27 | These are the `#define` values that can appear in the header file. The format of this file might change in a future release; see https://github.com/osvenskan/posix_ipc/issues/62 28 | 29 | - `POSIX_IPC_VERSION` - A string, e.g. "1.1.1" 30 | 31 | - `POSIX_IPC_DEBUG` - A boolean. If present, `posix_ipc` will print messages to `stderr` as it runs. (Use this with care; it's a developer-only feature and the implementation isn't very robust.) 32 | 33 | - `REALTIME_LIB_IS_NEEDED` - A boolean. If present, then the setup procedure will link to `librt` at build time. If absent, the setup procedure will not link to `librt`. 34 | 35 | - `PAGE_SIZE` - An integer, e.g. 8192 36 | 37 | - `SEM_GETVALUE_EXISTS` - A boolean. If present, then the OS supports `sem_getvalue()`, so `posix_ipc` can implement `Semaphore.value`. If not present, `sem_getvalue()` isn't supported, so `Semaphore.value` will raise an `AttributeError`. 38 | 39 | - `SEM_TIMEDWAIT_EXISTS` - A boolean. If present, then the OS supports `sem_timedwait()`, so `posix_ipc` can enable `Semaphore.acquire()` with timeouts other than 0 and infinite. If not present, `sem_timedwait()` isn't supported. (See [the documentation for `Semaphore.acquire()`](usage.md#semaphore-acquire-method.) 40 | 41 | - `SEM_VALUE_MAX` - An integer that describes the maximum value of a semaphore, e.g. 32767 42 | 43 | - `MESSAGE_QUEUE_SUPPORT_EXISTS` - A boolean. If present, then the host system implements POSIX message queues. If not present, `posix_ipc` can't offer any `MessageQueue` features. (Mac OS is an example of a platform that doesn't implement POSIX message queues.) 44 | 45 | - `QUEUE_MESSAGES_MAX_DEFAULT` - A integer, e.g. 10. When creating a message queue, this value is supplied for the queue's `max_messages` parameter if one isn't specified by the caller. 46 | 47 | - `QUEUE_MESSAGE_SIZE_MAX_DEFAULT` - A integer, e.g. 8192. When creating a message queue, this value is supplied for the queue's `max_message_size` parameter if one isn't specified by the caller. 48 | 49 | - `QUEUE_PRIORITY_MAX` - A integer, e.g. 32, that describes the largest priority value that a message can have. 50 | -------------------------------------------------------------------------------- /demos/demo1/ReadMe.md: -------------------------------------------------------------------------------- 1 | This demonstrates use of shared memory and semaphores via two applications 2 | named after Mrs. Premise and Mrs. Conclusion of the Monty Python sketch. 3 | 4 | Like those two characters, these programs chat back and forth and the result 5 | is a lot of nonsense. In this case, what the programs are saying isn't the 6 | interesting part. What's interesting is how they're doing it. 7 | 8 | Mrs. Premise and Mrs. Conclusion (the programs, not the sketch characters) 9 | communicate through POSIX shared memory with a semaphore to control access 10 | to that memory. 11 | 12 | Mrs. Premise starts things off by creating the shared memory and semaphore 13 | and writing a random string (the current time) to the memory. She then sits 14 | in a loop reading the memory. If it holds the same message she wrote, she 15 | does nothing. If it is a new message, it must be from Mrs. Conclusion. 16 | 17 | Meanwhile, Mrs. Conclusion is doing exactly the same thing, except that she 18 | assumes Mrs. Premise will write the first message. 19 | 20 | When either of these programs reads a new message, they write back an md5 21 | hash of that message. This serves two purposes. First, it ensures that 22 | subsequent messages are very different so that if a message somehow gets 23 | corrupted (say by being partially overwritten by the next message), it will 24 | not escape notice. Second, it ensures that corruption can be detected if 25 | it happens, because Mrs. Premise and Mrs. Conclusion can calculate what the 26 | other's response to their message should be. 27 | 28 | Since they use a semaphore to control access to the shared memory, Mrs. 29 | Premise and Mrs. Conclusion won't ever find their messages corrupted no 30 | matter how many messages they exchange. You can experiment with this by 31 | setting ITERATIONS in params.txt to a very large value. You can change 32 | LIVE_DANGEROUSLY (also in params.txt) to a non-zero value to tell Mrs. 33 | Premise and Mrs. Conclusion to run without using the semaphore. The shared 34 | memory will probably get corrupted in fewer than 1000 iterations. 35 | 36 | To run the demo, start Mrs. Premise first in one window and then run 37 | Mrs. Conclusion in another. 38 | 39 | ## The Fancy Version 40 | 41 | If you want to get fancy, you can play with C versions of Mrs. Premise and 42 | Mrs. Conclusion. The script make_all.sh will compile them for you. (Linux 43 | users will need to edit the script and uncomment the line for the 44 | Linux-specific linker option.) 45 | 46 | The resulting executables are called premise and conclusion and work exactly 47 | the same as their Python counterparts. You can have the two C programs talk 48 | to one another, or you can have premise.py talk to the C version of 49 | conclusion...the possibilities are endless. (Actually, there are only four 50 | possible combinations but "endless" sounds better.) 51 | -------------------------------------------------------------------------------- /demos/demo1/SampleIpcConversation.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/osvenskan/posix_ipc/e0ee0128534cfd774ff3091ad7f068264661d896/demos/demo1/SampleIpcConversation.png -------------------------------------------------------------------------------- /demos/demo1/cleanup.py: -------------------------------------------------------------------------------- 1 | import posix_ipc 2 | import utils 3 | 4 | params = utils.read_params() 5 | 6 | try: 7 | posix_ipc.unlink_shared_memory(params["SHARED_MEMORY_NAME"]) 8 | s = "memory segment %s removed" % params["SHARED_MEMORY_NAME"] 9 | print(s) 10 | except: 11 | print("memory doesn't need cleanup") 12 | 13 | 14 | try: 15 | posix_ipc.unlink_semaphore(params["SEMAPHORE_NAME"]) 16 | s = "semaphore %s removed" % params["SEMAPHORE_NAME"] 17 | print(s) 18 | except: 19 | print("semaphore doesn't need cleanup") 20 | 21 | 22 | print("\nAll clean!") 23 | -------------------------------------------------------------------------------- /demos/demo1/conclusion.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | #include "md5.h" 12 | #include "utils.h" 13 | 14 | static const char MY_NAME[] = "Mrs. Conclusion"; 15 | 16 | // Set up a Mrs. Premise & Mrs. Conclusion conversation. 17 | 18 | int main() { 19 | sem_t *the_semaphore = NULL; 20 | int rc; 21 | char s[1024]; 22 | int i; 23 | int done; 24 | int fd; 25 | void *pSharedMemory = NULL; 26 | char last_message_i_wrote[256]; 27 | char md5ified_message[256]; 28 | struct param_struct params; 29 | 30 | say(MY_NAME, "Oooo 'ello, I'm Mrs. Conclusion!"); 31 | 32 | read_params(¶ms); 33 | 34 | // Mrs. Premise has already created the semaphore and shared memory. 35 | // I just need to get handles to them. 36 | the_semaphore = sem_open(params.semaphore_name, 0); 37 | 38 | if (the_semaphore == SEM_FAILED) { 39 | the_semaphore = NULL; 40 | sprintf(s, "Getting a handle to the semaphore failed; errno is %d", errno); 41 | say(MY_NAME, s); 42 | } 43 | else { 44 | // get a handle to the shared memory 45 | fd = shm_open(params.shared_memory_name, O_RDWR, params.permissions); 46 | 47 | if (fd == -1) { 48 | sprintf(s, "Couldn't get a handle to the shared memory; errno is %d", errno); 49 | say(MY_NAME, s); 50 | } 51 | else { 52 | // mmap it. 53 | pSharedMemory = mmap((void *)0, (size_t)params.size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); 54 | if (pSharedMemory == MAP_FAILED) { 55 | sprintf(s, "MMapping the shared memory failed; errno is %d", errno); 56 | say(MY_NAME, s); 57 | } 58 | else { 59 | sprintf(s, "pSharedMemory = %p", pSharedMemory); 60 | say(MY_NAME, s); 61 | 62 | i = 0; 63 | done = 0; 64 | last_message_i_wrote[0] = '\0'; 65 | while (!done) { 66 | sprintf(s, "iteration %d", i); 67 | say(MY_NAME, s); 68 | 69 | // Wait for Mrs. Premise to free up the semaphore. 70 | rc = acquire_semaphore(MY_NAME, the_semaphore, params.live_dangerously); 71 | if (rc) 72 | done = 1; 73 | else { 74 | while ( (!rc) && \ 75 | (!strcmp((char *)pSharedMemory, last_message_i_wrote)) 76 | ) { 77 | // Nothing new; give Mrs. Premise another change to respond. 78 | sprintf(s, "Read %zu characters '%s'", strlen((char *)pSharedMemory), (char *)pSharedMemory); 79 | say(MY_NAME, s); 80 | say(MY_NAME, "Releasing the semaphore"); 81 | rc = release_semaphore(MY_NAME, the_semaphore, params.live_dangerously); 82 | if (!rc) { 83 | say(MY_NAME, "Waiting to acquire the semaphore"); 84 | rc = acquire_semaphore(MY_NAME, the_semaphore, params.live_dangerously); 85 | } 86 | } 87 | 88 | md5ify(last_message_i_wrote, md5ified_message); 89 | 90 | // I always accept the first message (when i == 0) 91 | if ( (i == 0) || (!strcmp(md5ified_message, (char *)pSharedMemory)) ) { 92 | // All is well 93 | i++; 94 | 95 | if (i == params.iterations) 96 | done = 1; 97 | 98 | // MD5 the reply and write back to Mrs. Premise. 99 | md5ify((char *)pSharedMemory, md5ified_message); 100 | 101 | // Write back to Mrs. Premise. 102 | sprintf(s, "Writing %zu characters '%s'", strlen(md5ified_message), md5ified_message); 103 | say(MY_NAME, s); 104 | 105 | strcpy((char *)pSharedMemory, md5ified_message); 106 | 107 | strcpy(last_message_i_wrote, md5ified_message); 108 | } 109 | else { 110 | sprintf(s, "Shared memory corruption after %d iterations.", i); 111 | say(MY_NAME, s); 112 | sprintf(s, "Mismatch; rc = %d, new message is '%s', expected '%s'.", rc, (char *)pSharedMemory, md5ified_message); 113 | say(MY_NAME, s); 114 | done = 1; 115 | } 116 | } 117 | 118 | // Release the semaphore. 119 | rc = release_semaphore(MY_NAME, the_semaphore, params.live_dangerously); 120 | if (rc) 121 | done = 1; 122 | } 123 | } 124 | // Un-mmap the memory 125 | rc = munmap(pSharedMemory, (size_t)params.size); 126 | if (rc) { 127 | sprintf(s, "Unmapping the memory failed; errno is %d", errno); 128 | say(MY_NAME, s); 129 | } 130 | 131 | // Close the shared memory segment's file descriptor 132 | if (-1 == close(fd)) { 133 | sprintf(s, "Closing memory's file descriptor failed; errno is %d", errno); 134 | say(MY_NAME, s); 135 | } 136 | } 137 | rc = sem_close(the_semaphore); 138 | if (rc) { 139 | sprintf(s, "Closing the semaphore failed; errno is %d", errno); 140 | say(MY_NAME, s); 141 | } 142 | } 143 | 144 | 145 | return 0; 146 | } 147 | -------------------------------------------------------------------------------- /demos/demo1/conclusion.py: -------------------------------------------------------------------------------- 1 | # Python modules 2 | import mmap 3 | import os 4 | import sys 5 | import hashlib 6 | 7 | # 3rd party modules 8 | import posix_ipc 9 | 10 | # Utils for this demo 11 | import utils 12 | 13 | 14 | utils.say("Oooo 'ello, I'm Mrs. Conclusion!") 15 | 16 | params = utils.read_params() 17 | 18 | # Mrs. Premise has already created the semaphore and shared memory. 19 | # I just need to get handles to them. 20 | memory = posix_ipc.SharedMemory(params["SHARED_MEMORY_NAME"]) 21 | semaphore = posix_ipc.Semaphore(params["SEMAPHORE_NAME"]) 22 | 23 | # MMap the shared memory 24 | mapfile = mmap.mmap(memory.fd, memory.size) 25 | 26 | # Once I've mmapped the file descriptor, I can close it without 27 | # interfering with the mmap. This also demonstrates that os.close() is a 28 | # perfectly legitimate alternative to the SharedMemory's close_fd() method. 29 | os.close(memory.fd) 30 | 31 | what_i_wrote = "" 32 | 33 | for i in range(0, params["ITERATIONS"]): 34 | utils.say("iteration %d" % i) 35 | if not params["LIVE_DANGEROUSLY"]: 36 | # Wait for Mrs. Premise to free up the semaphore. 37 | utils.say("Waiting to acquire the semaphore") 38 | semaphore.acquire() 39 | 40 | s = utils.read_from_memory(mapfile) 41 | 42 | while s == what_i_wrote: 43 | if not params["LIVE_DANGEROUSLY"]: 44 | # Release the semaphore... 45 | utils.say("Releasing the semaphore") 46 | semaphore.release() 47 | # ...and wait for it to become available again. 48 | utils.say("Waiting to acquire the semaphore") 49 | semaphore.acquire() 50 | 51 | s = utils.read_from_memory(mapfile) 52 | 53 | if what_i_wrote: 54 | what_i_wrote = what_i_wrote.encode() 55 | try: 56 | assert(s == hashlib.md5(what_i_wrote).hexdigest()) 57 | except AssertionError: 58 | raise AssertionError("Shared memory corruption after %d iterations." % i) 59 | 60 | s = s.encode() 61 | what_i_wrote = hashlib.md5(s).hexdigest() 62 | 63 | utils.write_to_memory(mapfile, what_i_wrote) 64 | 65 | if not params["LIVE_DANGEROUSLY"]: 66 | utils.say("Releasing the semaphore") 67 | semaphore.release() 68 | 69 | semaphore.close() 70 | mapfile.close() 71 | 72 | utils.say("") 73 | utils.say("%d iterations complete" % (i + 1)) 74 | -------------------------------------------------------------------------------- /demos/demo1/make_all.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # Linker opts should be blank for OS X, FreeBSD and OpenSolaris 4 | #LINKER_OPTIONS="" 5 | 6 | # On Linux, we must link with realtime and thread libraries 7 | LINKER_OPTIONS="-lrt -lpthread" 8 | 9 | gcc -Wall -c -o md5.o md5.c 10 | gcc -Wall -c -o utils.o utils.c 11 | gcc -Wall md5.o utils.o -o premise premise.c -L. $LINKER_OPTIONS 12 | gcc -Wall md5.o utils.o -o conclusion conclusion.c -L. $LINKER_OPTIONS 13 | -------------------------------------------------------------------------------- /demos/demo1/md5.c: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (C) 1999, 2000, 2002 Aladdin Enterprises. All rights reserved. 3 | 4 | This software is provided 'as-is', without any express or implied 5 | warranty. In no event will the authors be held liable for any damages 6 | arising from the use of this software. 7 | 8 | Permission is granted to anyone to use this software for any purpose, 9 | including commercial applications, and to alter it and redistribute it 10 | freely, subject to the following restrictions: 11 | 12 | 1. The origin of this software must not be misrepresented; you must not 13 | claim that you wrote the original software. If you use this software 14 | in a product, an acknowledgment in the product documentation would be 15 | appreciated but is not required. 16 | 2. Altered source versions must be plainly marked as such, and must not be 17 | misrepresented as being the original software. 18 | 3. This notice may not be removed or altered from any source distribution. 19 | 20 | L. Peter Deutsch 21 | ghost@aladdin.com 22 | 23 | */ 24 | /* $Id: md5.c,v 1.6 2002/04/13 19:20:28 lpd Exp $ */ 25 | /* 26 | Independent implementation of MD5 (RFC 1321). 27 | 28 | This code implements the MD5 Algorithm defined in RFC 1321, whose 29 | text is available at 30 | http://www.ietf.org/rfc/rfc1321.txt 31 | The code is derived from the text of the RFC, including the test suite 32 | (section A.5) but excluding the rest of Appendix A. It does not include 33 | any code or documentation that is identified in the RFC as being 34 | copyrighted. 35 | 36 | The original and principal author of md5.c is L. Peter Deutsch 37 | . Other authors are noted in the change history 38 | that follows (in reverse chronological order): 39 | 40 | 2002-04-13 lpd Clarified derivation from RFC 1321; now handles byte order 41 | either statically or dynamically; added missing #include 42 | in library. 43 | 2002-03-11 lpd Corrected argument list for main(), and added int return 44 | type, in test program and T value program. 45 | 2002-02-21 lpd Added missing #include in test program. 46 | 2000-07-03 lpd Patched to eliminate warnings about "constant is 47 | unsigned in ANSI C, signed in traditional"; made test program 48 | self-checking. 49 | 1999-11-04 lpd Edited comments slightly for automatic TOC extraction. 50 | 1999-10-18 lpd Fixed typo in header comment (ansi2knr rather than md5). 51 | 1999-05-03 lpd Original version. 52 | */ 53 | 54 | #include "md5.h" 55 | #include 56 | 57 | #undef BYTE_ORDER /* 1 = big-endian, -1 = little-endian, 0 = unknown */ 58 | #ifdef ARCH_IS_BIG_ENDIAN 59 | # define BYTE_ORDER (ARCH_IS_BIG_ENDIAN ? 1 : -1) 60 | #else 61 | # define BYTE_ORDER 0 62 | #endif 63 | 64 | #define T_MASK ((md5_word_t)~0) 65 | #define T1 /* 0xd76aa478 */ (T_MASK ^ 0x28955b87) 66 | #define T2 /* 0xe8c7b756 */ (T_MASK ^ 0x173848a9) 67 | #define T3 0x242070db 68 | #define T4 /* 0xc1bdceee */ (T_MASK ^ 0x3e423111) 69 | #define T5 /* 0xf57c0faf */ (T_MASK ^ 0x0a83f050) 70 | #define T6 0x4787c62a 71 | #define T7 /* 0xa8304613 */ (T_MASK ^ 0x57cfb9ec) 72 | #define T8 /* 0xfd469501 */ (T_MASK ^ 0x02b96afe) 73 | #define T9 0x698098d8 74 | #define T10 /* 0x8b44f7af */ (T_MASK ^ 0x74bb0850) 75 | #define T11 /* 0xffff5bb1 */ (T_MASK ^ 0x0000a44e) 76 | #define T12 /* 0x895cd7be */ (T_MASK ^ 0x76a32841) 77 | #define T13 0x6b901122 78 | #define T14 /* 0xfd987193 */ (T_MASK ^ 0x02678e6c) 79 | #define T15 /* 0xa679438e */ (T_MASK ^ 0x5986bc71) 80 | #define T16 0x49b40821 81 | #define T17 /* 0xf61e2562 */ (T_MASK ^ 0x09e1da9d) 82 | #define T18 /* 0xc040b340 */ (T_MASK ^ 0x3fbf4cbf) 83 | #define T19 0x265e5a51 84 | #define T20 /* 0xe9b6c7aa */ (T_MASK ^ 0x16493855) 85 | #define T21 /* 0xd62f105d */ (T_MASK ^ 0x29d0efa2) 86 | #define T22 0x02441453 87 | #define T23 /* 0xd8a1e681 */ (T_MASK ^ 0x275e197e) 88 | #define T24 /* 0xe7d3fbc8 */ (T_MASK ^ 0x182c0437) 89 | #define T25 0x21e1cde6 90 | #define T26 /* 0xc33707d6 */ (T_MASK ^ 0x3cc8f829) 91 | #define T27 /* 0xf4d50d87 */ (T_MASK ^ 0x0b2af278) 92 | #define T28 0x455a14ed 93 | #define T29 /* 0xa9e3e905 */ (T_MASK ^ 0x561c16fa) 94 | #define T30 /* 0xfcefa3f8 */ (T_MASK ^ 0x03105c07) 95 | #define T31 0x676f02d9 96 | #define T32 /* 0x8d2a4c8a */ (T_MASK ^ 0x72d5b375) 97 | #define T33 /* 0xfffa3942 */ (T_MASK ^ 0x0005c6bd) 98 | #define T34 /* 0x8771f681 */ (T_MASK ^ 0x788e097e) 99 | #define T35 0x6d9d6122 100 | #define T36 /* 0xfde5380c */ (T_MASK ^ 0x021ac7f3) 101 | #define T37 /* 0xa4beea44 */ (T_MASK ^ 0x5b4115bb) 102 | #define T38 0x4bdecfa9 103 | #define T39 /* 0xf6bb4b60 */ (T_MASK ^ 0x0944b49f) 104 | #define T40 /* 0xbebfbc70 */ (T_MASK ^ 0x4140438f) 105 | #define T41 0x289b7ec6 106 | #define T42 /* 0xeaa127fa */ (T_MASK ^ 0x155ed805) 107 | #define T43 /* 0xd4ef3085 */ (T_MASK ^ 0x2b10cf7a) 108 | #define T44 0x04881d05 109 | #define T45 /* 0xd9d4d039 */ (T_MASK ^ 0x262b2fc6) 110 | #define T46 /* 0xe6db99e5 */ (T_MASK ^ 0x1924661a) 111 | #define T47 0x1fa27cf8 112 | #define T48 /* 0xc4ac5665 */ (T_MASK ^ 0x3b53a99a) 113 | #define T49 /* 0xf4292244 */ (T_MASK ^ 0x0bd6ddbb) 114 | #define T50 0x432aff97 115 | #define T51 /* 0xab9423a7 */ (T_MASK ^ 0x546bdc58) 116 | #define T52 /* 0xfc93a039 */ (T_MASK ^ 0x036c5fc6) 117 | #define T53 0x655b59c3 118 | #define T54 /* 0x8f0ccc92 */ (T_MASK ^ 0x70f3336d) 119 | #define T55 /* 0xffeff47d */ (T_MASK ^ 0x00100b82) 120 | #define T56 /* 0x85845dd1 */ (T_MASK ^ 0x7a7ba22e) 121 | #define T57 0x6fa87e4f 122 | #define T58 /* 0xfe2ce6e0 */ (T_MASK ^ 0x01d3191f) 123 | #define T59 /* 0xa3014314 */ (T_MASK ^ 0x5cfebceb) 124 | #define T60 0x4e0811a1 125 | #define T61 /* 0xf7537e82 */ (T_MASK ^ 0x08ac817d) 126 | #define T62 /* 0xbd3af235 */ (T_MASK ^ 0x42c50dca) 127 | #define T63 0x2ad7d2bb 128 | #define T64 /* 0xeb86d391 */ (T_MASK ^ 0x14792c6e) 129 | 130 | 131 | static void 132 | md5_process(md5_state_t *pms, const md5_byte_t *data /*[64]*/) 133 | { 134 | md5_word_t 135 | a = pms->abcd[0], b = pms->abcd[1], 136 | c = pms->abcd[2], d = pms->abcd[3]; 137 | md5_word_t t; 138 | #if BYTE_ORDER > 0 139 | /* Define storage only for big-endian CPUs. */ 140 | md5_word_t X[16]; 141 | #else 142 | /* Define storage for little-endian or both types of CPUs. */ 143 | md5_word_t xbuf[16]; 144 | const md5_word_t *X; 145 | #endif 146 | 147 | { 148 | #if BYTE_ORDER == 0 149 | /* 150 | * Determine dynamically whether this is a big-endian or 151 | * little-endian machine, since we can use a more efficient 152 | * algorithm on the latter. 153 | */ 154 | static const int w = 1; 155 | 156 | if (*((const md5_byte_t *)&w)) /* dynamic little-endian */ 157 | #endif 158 | #if BYTE_ORDER <= 0 /* little-endian */ 159 | { 160 | /* 161 | * On little-endian machines, we can process properly aligned 162 | * data without copying it. 163 | */ 164 | if (!((data - (const md5_byte_t *)0) & 3)) { 165 | /* data are properly aligned */ 166 | X = (const md5_word_t *)data; 167 | } else { 168 | /* not aligned */ 169 | memcpy(xbuf, data, 64); 170 | X = xbuf; 171 | } 172 | } 173 | #endif 174 | #if BYTE_ORDER == 0 175 | else /* dynamic big-endian */ 176 | #endif 177 | #if BYTE_ORDER >= 0 /* big-endian */ 178 | { 179 | /* 180 | * On big-endian machines, we must arrange the bytes in the 181 | * right order. 182 | */ 183 | const md5_byte_t *xp = data; 184 | int i; 185 | 186 | # if BYTE_ORDER == 0 187 | X = xbuf; /* (dynamic only) */ 188 | # else 189 | # define xbuf X /* (static only) */ 190 | # endif 191 | for (i = 0; i < 16; ++i, xp += 4) 192 | xbuf[i] = xp[0] + (xp[1] << 8) + (xp[2] << 16) + (xp[3] << 24); 193 | } 194 | #endif 195 | } 196 | 197 | #define ROTATE_LEFT(x, n) (((x) << (n)) | ((x) >> (32 - (n)))) 198 | 199 | /* Round 1. */ 200 | /* Let [abcd k s i] denote the operation 201 | a = b + ((a + F(b,c,d) + X[k] + T[i]) <<< s). */ 202 | #define F(x, y, z) (((x) & (y)) | (~(x) & (z))) 203 | #define SET(a, b, c, d, k, s, Ti)\ 204 | t = a + F(b,c,d) + X[k] + Ti;\ 205 | a = ROTATE_LEFT(t, s) + b 206 | /* Do the following 16 operations. */ 207 | SET(a, b, c, d, 0, 7, T1); 208 | SET(d, a, b, c, 1, 12, T2); 209 | SET(c, d, a, b, 2, 17, T3); 210 | SET(b, c, d, a, 3, 22, T4); 211 | SET(a, b, c, d, 4, 7, T5); 212 | SET(d, a, b, c, 5, 12, T6); 213 | SET(c, d, a, b, 6, 17, T7); 214 | SET(b, c, d, a, 7, 22, T8); 215 | SET(a, b, c, d, 8, 7, T9); 216 | SET(d, a, b, c, 9, 12, T10); 217 | SET(c, d, a, b, 10, 17, T11); 218 | SET(b, c, d, a, 11, 22, T12); 219 | SET(a, b, c, d, 12, 7, T13); 220 | SET(d, a, b, c, 13, 12, T14); 221 | SET(c, d, a, b, 14, 17, T15); 222 | SET(b, c, d, a, 15, 22, T16); 223 | #undef SET 224 | 225 | /* Round 2. */ 226 | /* Let [abcd k s i] denote the operation 227 | a = b + ((a + G(b,c,d) + X[k] + T[i]) <<< s). */ 228 | #define G(x, y, z) (((x) & (z)) | ((y) & ~(z))) 229 | #define SET(a, b, c, d, k, s, Ti)\ 230 | t = a + G(b,c,d) + X[k] + Ti;\ 231 | a = ROTATE_LEFT(t, s) + b 232 | /* Do the following 16 operations. */ 233 | SET(a, b, c, d, 1, 5, T17); 234 | SET(d, a, b, c, 6, 9, T18); 235 | SET(c, d, a, b, 11, 14, T19); 236 | SET(b, c, d, a, 0, 20, T20); 237 | SET(a, b, c, d, 5, 5, T21); 238 | SET(d, a, b, c, 10, 9, T22); 239 | SET(c, d, a, b, 15, 14, T23); 240 | SET(b, c, d, a, 4, 20, T24); 241 | SET(a, b, c, d, 9, 5, T25); 242 | SET(d, a, b, c, 14, 9, T26); 243 | SET(c, d, a, b, 3, 14, T27); 244 | SET(b, c, d, a, 8, 20, T28); 245 | SET(a, b, c, d, 13, 5, T29); 246 | SET(d, a, b, c, 2, 9, T30); 247 | SET(c, d, a, b, 7, 14, T31); 248 | SET(b, c, d, a, 12, 20, T32); 249 | #undef SET 250 | 251 | /* Round 3. */ 252 | /* Let [abcd k s t] denote the operation 253 | a = b + ((a + H(b,c,d) + X[k] + T[i]) <<< s). */ 254 | #define H(x, y, z) ((x) ^ (y) ^ (z)) 255 | #define SET(a, b, c, d, k, s, Ti)\ 256 | t = a + H(b,c,d) + X[k] + Ti;\ 257 | a = ROTATE_LEFT(t, s) + b 258 | /* Do the following 16 operations. */ 259 | SET(a, b, c, d, 5, 4, T33); 260 | SET(d, a, b, c, 8, 11, T34); 261 | SET(c, d, a, b, 11, 16, T35); 262 | SET(b, c, d, a, 14, 23, T36); 263 | SET(a, b, c, d, 1, 4, T37); 264 | SET(d, a, b, c, 4, 11, T38); 265 | SET(c, d, a, b, 7, 16, T39); 266 | SET(b, c, d, a, 10, 23, T40); 267 | SET(a, b, c, d, 13, 4, T41); 268 | SET(d, a, b, c, 0, 11, T42); 269 | SET(c, d, a, b, 3, 16, T43); 270 | SET(b, c, d, a, 6, 23, T44); 271 | SET(a, b, c, d, 9, 4, T45); 272 | SET(d, a, b, c, 12, 11, T46); 273 | SET(c, d, a, b, 15, 16, T47); 274 | SET(b, c, d, a, 2, 23, T48); 275 | #undef SET 276 | 277 | /* Round 4. */ 278 | /* Let [abcd k s t] denote the operation 279 | a = b + ((a + I(b,c,d) + X[k] + T[i]) <<< s). */ 280 | #define I(x, y, z) ((y) ^ ((x) | ~(z))) 281 | #define SET(a, b, c, d, k, s, Ti)\ 282 | t = a + I(b,c,d) + X[k] + Ti;\ 283 | a = ROTATE_LEFT(t, s) + b 284 | /* Do the following 16 operations. */ 285 | SET(a, b, c, d, 0, 6, T49); 286 | SET(d, a, b, c, 7, 10, T50); 287 | SET(c, d, a, b, 14, 15, T51); 288 | SET(b, c, d, a, 5, 21, T52); 289 | SET(a, b, c, d, 12, 6, T53); 290 | SET(d, a, b, c, 3, 10, T54); 291 | SET(c, d, a, b, 10, 15, T55); 292 | SET(b, c, d, a, 1, 21, T56); 293 | SET(a, b, c, d, 8, 6, T57); 294 | SET(d, a, b, c, 15, 10, T58); 295 | SET(c, d, a, b, 6, 15, T59); 296 | SET(b, c, d, a, 13, 21, T60); 297 | SET(a, b, c, d, 4, 6, T61); 298 | SET(d, a, b, c, 11, 10, T62); 299 | SET(c, d, a, b, 2, 15, T63); 300 | SET(b, c, d, a, 9, 21, T64); 301 | #undef SET 302 | 303 | /* Then perform the following additions. (That is increment each 304 | of the four registers by the value it had before this block 305 | was started.) */ 306 | pms->abcd[0] += a; 307 | pms->abcd[1] += b; 308 | pms->abcd[2] += c; 309 | pms->abcd[3] += d; 310 | } 311 | 312 | void 313 | md5_init(md5_state_t *pms) 314 | { 315 | pms->count[0] = pms->count[1] = 0; 316 | pms->abcd[0] = 0x67452301; 317 | pms->abcd[1] = /*0xefcdab89*/ T_MASK ^ 0x10325476; 318 | pms->abcd[2] = /*0x98badcfe*/ T_MASK ^ 0x67452301; 319 | pms->abcd[3] = 0x10325476; 320 | } 321 | 322 | void 323 | md5_append(md5_state_t *pms, const md5_byte_t *data, int nbytes) 324 | { 325 | const md5_byte_t *p = data; 326 | int left = nbytes; 327 | int offset = (pms->count[0] >> 3) & 63; 328 | md5_word_t nbits = (md5_word_t)(nbytes << 3); 329 | 330 | if (nbytes <= 0) 331 | return; 332 | 333 | /* Update the message length. */ 334 | pms->count[1] += nbytes >> 29; 335 | pms->count[0] += nbits; 336 | if (pms->count[0] < nbits) 337 | pms->count[1]++; 338 | 339 | /* Process an initial partial block. */ 340 | if (offset) { 341 | int copy = (offset + nbytes > 64 ? 64 - offset : nbytes); 342 | 343 | memcpy(pms->buf + offset, p, copy); 344 | if (offset + copy < 64) 345 | return; 346 | p += copy; 347 | left -= copy; 348 | md5_process(pms, pms->buf); 349 | } 350 | 351 | /* Process full blocks. */ 352 | for (; left >= 64; p += 64, left -= 64) 353 | md5_process(pms, p); 354 | 355 | /* Process a final partial block. */ 356 | if (left) 357 | memcpy(pms->buf, p, left); 358 | } 359 | 360 | void 361 | md5_finish(md5_state_t *pms, md5_byte_t digest[16]) 362 | { 363 | static const md5_byte_t pad[64] = { 364 | 0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 365 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 366 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 367 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 368 | }; 369 | md5_byte_t data[8]; 370 | int i; 371 | 372 | /* Save the length before padding. */ 373 | for (i = 0; i < 8; ++i) 374 | data[i] = (md5_byte_t)(pms->count[i >> 2] >> ((i & 3) << 3)); 375 | /* Pad to 56 bytes mod 64. */ 376 | md5_append(pms, pad, ((55 - (pms->count[0] >> 3)) & 63) + 1); 377 | /* Append the length. */ 378 | md5_append(pms, data, 8); 379 | for (i = 0; i < 16; ++i) 380 | digest[i] = (md5_byte_t)(pms->abcd[i >> 2] >> ((i & 3) << 3)); 381 | } 382 | -------------------------------------------------------------------------------- /demos/demo1/md5.h: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (C) 1999, 2002 Aladdin Enterprises. All rights reserved. 3 | 4 | This software is provided 'as-is', without any express or implied 5 | warranty. In no event will the authors be held liable for any damages 6 | arising from the use of this software. 7 | 8 | Permission is granted to anyone to use this software for any purpose, 9 | including commercial applications, and to alter it and redistribute it 10 | freely, subject to the following restrictions: 11 | 12 | 1. The origin of this software must not be misrepresented; you must not 13 | claim that you wrote the original software. If you use this software 14 | in a product, an acknowledgment in the product documentation would be 15 | appreciated but is not required. 16 | 2. Altered source versions must be plainly marked as such, and must not be 17 | misrepresented as being the original software. 18 | 3. This notice may not be removed or altered from any source distribution. 19 | 20 | L. Peter Deutsch 21 | ghost@aladdin.com 22 | 23 | */ 24 | /* $Id: md5.h,v 1.4 2002/04/13 19:20:28 lpd Exp $ */ 25 | /* 26 | Independent implementation of MD5 (RFC 1321). 27 | 28 | This code implements the MD5 Algorithm defined in RFC 1321, whose 29 | text is available at 30 | http://www.ietf.org/rfc/rfc1321.txt 31 | The code is derived from the text of the RFC, including the test suite 32 | (section A.5) but excluding the rest of Appendix A. It does not include 33 | any code or documentation that is identified in the RFC as being 34 | copyrighted. 35 | 36 | The original and principal author of md5.h is L. Peter Deutsch 37 | . Other authors are noted in the change history 38 | that follows (in reverse chronological order): 39 | 40 | 2002-04-13 lpd Removed support for non-ANSI compilers; removed 41 | references to Ghostscript; clarified derivation from RFC 1321; 42 | now handles byte order either statically or dynamically. 43 | 1999-11-04 lpd Edited comments slightly for automatic TOC extraction. 44 | 1999-10-18 lpd Fixed typo in header comment (ansi2knr rather than md5); 45 | added conditionalization for C++ compilation from Martin 46 | Purschke . 47 | 1999-05-03 lpd Original version. 48 | */ 49 | 50 | #ifndef md5_INCLUDED 51 | # define md5_INCLUDED 52 | 53 | /* 54 | * This package supports both compile-time and run-time determination of CPU 55 | * byte order. If ARCH_IS_BIG_ENDIAN is defined as 0, the code will be 56 | * compiled to run only on little-endian CPUs; if ARCH_IS_BIG_ENDIAN is 57 | * defined as non-zero, the code will be compiled to run only on big-endian 58 | * CPUs; if ARCH_IS_BIG_ENDIAN is not defined, the code will be compiled to 59 | * run on either big- or little-endian CPUs, but will run slightly less 60 | * efficiently on either one than if ARCH_IS_BIG_ENDIAN is defined. 61 | */ 62 | 63 | typedef unsigned char md5_byte_t; /* 8-bit byte */ 64 | typedef unsigned int md5_word_t; /* 32-bit word */ 65 | 66 | /* Define the state of the MD5 Algorithm. */ 67 | typedef struct md5_state_s { 68 | md5_word_t count[2]; /* message length in bits, lsw first */ 69 | md5_word_t abcd[4]; /* digest buffer */ 70 | md5_byte_t buf[64]; /* accumulate block */ 71 | } md5_state_t; 72 | 73 | #ifdef __cplusplus 74 | extern "C" 75 | { 76 | #endif 77 | 78 | /* Initialize the algorithm. */ 79 | void md5_init(md5_state_t *pms); 80 | 81 | /* Append a string to the message. */ 82 | void md5_append(md5_state_t *pms, const md5_byte_t *data, int nbytes); 83 | 84 | /* Finish the message and return the digest. */ 85 | void md5_finish(md5_state_t *pms, md5_byte_t digest[16]); 86 | 87 | #ifdef __cplusplus 88 | } /* end extern "C" */ 89 | #endif 90 | 91 | #endif /* md5_INCLUDED */ 92 | -------------------------------------------------------------------------------- /demos/demo1/params.txt: -------------------------------------------------------------------------------- 1 | # These parameters control how Mrs. Premise and Mrs. Conclusion behave. 2 | 3 | # ITERATIONS is the number of times they'll talk to one another. 4 | # LIVE_DANGEROUSLY is a Boolean (0 or 1); if set to 1 the programs 5 | # won't use the semaphore to coordinate access to the shared 6 | # memory. Corruption will likely result. 7 | # SEMAPHORE_NAME is the name to be used for the semaphore. 8 | # SHARED_MEMORY_NAME is the name to be used for the shared memory. 9 | # PERMISSIONS are in octal (note the leading 0). 10 | # SHM_SIZE is the size of the shared memory segment in bytes. 11 | 12 | ITERATIONS=1000 13 | LIVE_DANGEROUSLY=0 14 | SEMAPHORE_NAME=/wuthering_heights 15 | SHARED_MEMORY_NAME=/four_yorkshiremen 16 | PERMISSIONS=0600 17 | SHM_SIZE=4096 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /demos/demo1/premise.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | #include "md5.h" 13 | #include "utils.h" 14 | 15 | const char MY_NAME[] = "Mrs. Premise"; 16 | 17 | // Set up a Mrs. Premise & Mrs. Conclusion conversation. 18 | 19 | void get_current_time(char *); 20 | 21 | int main() { 22 | sem_t *pSemaphore = NULL; 23 | int rc; 24 | char s[1024]; 25 | void *pSharedMemory = NULL; 26 | char last_message_i_wrote[256]; 27 | char md5ified_message[256]; 28 | int i = 0; 29 | int done = 0; 30 | int fd; 31 | struct param_struct params; 32 | 33 | say(MY_NAME, "Oooo 'ello, I'm Mrs. Premise!"); 34 | 35 | read_params(¶ms); 36 | 37 | // Create the shared memory 38 | fd = shm_open(params.shared_memory_name, O_RDWR | O_CREAT | O_EXCL, params.permissions); 39 | 40 | if (fd == -1) { 41 | fd = 0; 42 | sprintf(s, "Creating the shared memory failed; errno is %d", errno); 43 | say(MY_NAME, s); 44 | } 45 | else { 46 | // The memory is created as a file that's 0 bytes long. Resize it. 47 | rc = ftruncate(fd, params.size); 48 | if (rc) { 49 | sprintf(s, "Resizing the shared memory failed; errno is %d", errno); 50 | say(MY_NAME, s); 51 | } 52 | else { 53 | // MMap the shared memory 54 | //void *mmap(void *start, size_t length, int prot, int flags, int fd, off_t offset); 55 | pSharedMemory = mmap((void *)0, (size_t)params.size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); 56 | if (pSharedMemory == MAP_FAILED) { 57 | pSharedMemory = NULL; 58 | sprintf(s, "MMapping the shared memory failed; errno is %d", errno); 59 | say(MY_NAME, s); 60 | } 61 | else { 62 | sprintf(s, "pSharedMemory = %p", pSharedMemory); 63 | say(MY_NAME, s); 64 | } 65 | } 66 | } 67 | 68 | if (pSharedMemory) { 69 | // Create the semaphore 70 | pSemaphore = sem_open(params.semaphore_name, O_CREAT, params.permissions, 0); 71 | 72 | if (pSemaphore == SEM_FAILED) { 73 | sprintf(s, "Creating the semaphore failed; errno is %d", errno); 74 | say(MY_NAME, s); 75 | } 76 | else { 77 | sprintf(s, "the semaphore is %p", (void *)pSemaphore); 78 | say(MY_NAME, s); 79 | 80 | // I seed the shared memory with a random string (the current time). 81 | get_current_time(s); 82 | 83 | strcpy((char *)pSharedMemory, s); 84 | strcpy(last_message_i_wrote, s); 85 | 86 | sprintf(s, "Wrote %zu characters: %s", strlen(last_message_i_wrote), last_message_i_wrote); 87 | say(MY_NAME, s); 88 | 89 | i = 0; 90 | while (!done) { 91 | sprintf(s, "iteration %d", i); 92 | say(MY_NAME, s); 93 | 94 | // Release the semaphore... 95 | rc = release_semaphore(MY_NAME, pSemaphore, params.live_dangerously); 96 | // ...and wait for it to become available again. In real code 97 | // I might want to sleep briefly before calling .acquire() in 98 | // order to politely give other processes an opportunity to grab 99 | // the semaphore while it is free so as to avoid starvation. But 100 | // this code is meant to be a stress test that maximizes the 101 | // opportunity for shared memory corruption and politeness is 102 | // not helpful in stress tests. 103 | if (!rc) 104 | rc = acquire_semaphore(MY_NAME, pSemaphore, params.live_dangerously); 105 | 106 | if (rc) 107 | done = 1; 108 | else { 109 | // I keep checking the shared memory until something new has 110 | // been written. 111 | while ( (!rc) && \ 112 | (!strcmp((char *)pSharedMemory, last_message_i_wrote)) 113 | ) { 114 | // Nothing new; give Mrs. Conclusion another change to respond. 115 | sprintf(s, "Read %zu characters '%s'", strlen((char *)pSharedMemory), (char *)pSharedMemory); 116 | say(MY_NAME, s); 117 | rc = release_semaphore(MY_NAME, pSemaphore, params.live_dangerously); 118 | if (!rc) { 119 | rc = acquire_semaphore(MY_NAME, pSemaphore, params.live_dangerously); 120 | } 121 | } 122 | 123 | 124 | if (rc) 125 | done = 1; 126 | else { 127 | // What I read must be the md5 of what I wrote or something's 128 | // gone wrong. 129 | md5ify(last_message_i_wrote, md5ified_message); 130 | 131 | if (strcmp(md5ified_message, (char *)pSharedMemory) == 0) { 132 | // Yes, the message is OK 133 | i++; 134 | if (i == params.iterations) 135 | done = 1; 136 | 137 | // MD5 the reply and write back to Mrs. Conclusion. 138 | md5ify(md5ified_message, md5ified_message); 139 | 140 | sprintf(s, "Writing %zu characters '%s'", strlen(md5ified_message), md5ified_message); 141 | say(MY_NAME, s); 142 | 143 | strcpy((char *)pSharedMemory, md5ified_message); 144 | strcpy((char *)last_message_i_wrote, md5ified_message); 145 | } 146 | else { 147 | sprintf(s, "Shared memory corruption after %d iterations.", i); 148 | say(MY_NAME, s); 149 | sprintf(s, "Mismatch; new message is '%s', expected '%s'.", (char *)pSharedMemory, md5ified_message); 150 | say(MY_NAME, s); 151 | done = 1; 152 | } 153 | } 154 | } 155 | } 156 | 157 | // Announce for one last time that the semaphore is free again so that 158 | // Mrs. Conclusion can exit. 159 | say(MY_NAME, "Final release of the semaphore followed by a 5 second pause"); 160 | rc = release_semaphore(MY_NAME, pSemaphore, params.live_dangerously); 161 | sleep(5); 162 | // ...before beginning to wait until it is free again. 163 | // Technically, this is bad practice. It's possible that on a 164 | // heavily loaded machine, Mrs. Conclusion wouldn't get a chance 165 | // to acquire the semaphore. There really ought to be a loop here 166 | // that waits for some sort of goodbye message but for purposes of 167 | // simplicity I'm skipping that. 168 | 169 | say(MY_NAME, "Final wait to acquire the semaphore"); 170 | rc = acquire_semaphore(MY_NAME, pSemaphore, params.live_dangerously); 171 | if (!rc) { 172 | say(MY_NAME, "Destroying the shared memory."); 173 | 174 | // Un-mmap the memory... 175 | rc = munmap(pSharedMemory, (size_t)params.size); 176 | if (rc) { 177 | sprintf(s, "Unmapping the memory failed; errno is %d", errno); 178 | say(MY_NAME, s); 179 | } 180 | 181 | // ...close the file descriptor... 182 | if (-1 == close(fd)) { 183 | sprintf(s, "Closing the memory's file descriptor failed; errno is %d", errno); 184 | say(MY_NAME, s); 185 | } 186 | 187 | // ...and destroy the shared memory. 188 | rc = shm_unlink(params.shared_memory_name); 189 | if (rc) { 190 | sprintf(s, "Unlinking the memory failed; errno is %d", errno); 191 | say(MY_NAME, s); 192 | } 193 | } 194 | } 195 | 196 | say(MY_NAME, "Destroying the semaphore."); 197 | // Clean up the semaphore 198 | rc = sem_close(pSemaphore); 199 | if (rc) { 200 | sprintf(s, "Closing the semaphore failed; errno is %d", errno); 201 | say(MY_NAME, s); 202 | } 203 | rc = sem_unlink(params.semaphore_name); 204 | if (rc) { 205 | sprintf(s, "Unlinking the semaphore failed; errno is %d", errno); 206 | say(MY_NAME, s); 207 | } 208 | } 209 | return 0; 210 | } 211 | 212 | 213 | void get_current_time(char *s) { 214 | time_t the_time; 215 | struct tm *the_localtime; 216 | char *pAscTime; 217 | 218 | the_time = time(NULL); 219 | the_localtime = localtime(&the_time); 220 | pAscTime = asctime(the_localtime); 221 | 222 | strcpy(s, pAscTime); 223 | } 224 | -------------------------------------------------------------------------------- /demos/demo1/premise.py: -------------------------------------------------------------------------------- 1 | # Python modules 2 | import time 3 | import mmap 4 | import hashlib 5 | 6 | # 3rd party modules 7 | import posix_ipc 8 | 9 | # Utils for this demo 10 | import utils 11 | 12 | 13 | utils.say("Oooo 'ello, I'm Mrs. Premise!") 14 | 15 | params = utils.read_params() 16 | 17 | # Create the shared memory and the semaphore. 18 | memory = posix_ipc.SharedMemory(params["SHARED_MEMORY_NAME"], posix_ipc.O_CREX, 19 | size=params["SHM_SIZE"]) 20 | semaphore = posix_ipc.Semaphore(params["SEMAPHORE_NAME"], posix_ipc.O_CREX) 21 | 22 | # MMap the shared memory 23 | mapfile = mmap.mmap(memory.fd, memory.size) 24 | 25 | # Once I've mmapped the file descriptor, I can close it without 26 | # interfering with the mmap. 27 | memory.close_fd() 28 | 29 | # I seed the shared memory with a random string (the current time). 30 | what_i_wrote = time.asctime() 31 | utils.write_to_memory(mapfile, what_i_wrote) 32 | 33 | for i in range(params["ITERATIONS"]): 34 | utils.say("iteration %d" % i) 35 | if not params["LIVE_DANGEROUSLY"]: 36 | # Release the semaphore... 37 | utils.say("Releasing the semaphore") 38 | semaphore.release() 39 | # ...and wait for it to become available again. In real code 40 | # I might want to sleep briefly before calling .acquire() in 41 | # order to politely give other processes an opportunity to grab 42 | # the semaphore while it is free so as to avoid starvation. But 43 | # this code is meant to be a stress test that maximizes the 44 | # opportunity for shared memory corruption and politeness is 45 | # not helpful in stress tests. 46 | utils.say("Waiting to acquire the semaphore") 47 | semaphore.acquire() 48 | 49 | s = utils.read_from_memory(mapfile) 50 | 51 | # I keep checking the shared memory until something new has 52 | # been written. 53 | while s == what_i_wrote: 54 | # Nothing new; give Mrs. Conclusion another chance to respond. 55 | if not params["LIVE_DANGEROUSLY"]: 56 | utils.say("Releasing the semaphore") 57 | semaphore.release() 58 | utils.say("Waiting to acquire the semaphore") 59 | semaphore.acquire() 60 | 61 | s = utils.read_from_memory(mapfile) 62 | 63 | # What I read must be the md5 of what I wrote or something's 64 | # gone wrong. 65 | what_i_wrote = what_i_wrote.encode() 66 | 67 | try: 68 | assert(s == hashlib.md5(what_i_wrote).hexdigest()) 69 | except AssertionError: 70 | raise AssertionError("Shared memory corruption after %d iterations." % i) 71 | 72 | # MD5 the reply and write back to Mrs. Conclusion. 73 | s = s.encode() 74 | what_i_wrote = hashlib.md5(s).hexdigest() 75 | utils.write_to_memory(mapfile, what_i_wrote) 76 | 77 | utils.say("") 78 | utils.say("%d iterations complete" % (i + 1)) 79 | 80 | # Announce for one last time that the semaphore is free again so that 81 | # Mrs. Conclusion can exit. 82 | if not params["LIVE_DANGEROUSLY"]: 83 | utils.say("") 84 | utils.say("Final release of the semaphore followed by a 5 second pause") 85 | semaphore.release() 86 | time.sleep(5) 87 | # ...before beginning to wait until it is free again. 88 | # Technically, this is bad practice. It's possible that on a 89 | # heavily loaded machine, Mrs. Conclusion wouldn't get a chance 90 | # to acquire the semaphore. There really ought to be a loop here 91 | # that waits for some sort of goodbye message but for purposes of 92 | # simplicity I'm skipping that. 93 | utils.say("Final wait to acquire the semaphore") 94 | semaphore.acquire() 95 | 96 | utils.say("Destroying semaphore and shared memory.") 97 | mapfile.close() 98 | # I could call memory.unlink() here but in order to demonstrate 99 | # unlinking at the module level I'll do it that way. 100 | posix_ipc.unlink_shared_memory(params["SHARED_MEMORY_NAME"]) 101 | 102 | semaphore.release() 103 | 104 | # I could also unlink the semaphore by calling 105 | # posix_ipc.unlink_semaphore() but I'll do it this way instead. 106 | semaphore.unlink() 107 | -------------------------------------------------------------------------------- /demos/demo1/utils.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #include "utils.h" 10 | #include "md5.h" 11 | 12 | 13 | void md5ify(char *inString, char *outString) { 14 | md5_state_t state; 15 | md5_byte_t digest[16]; 16 | int i; 17 | 18 | md5_init(&state); 19 | md5_append(&state, (const md5_byte_t *)inString, strlen(inString)); 20 | md5_finish(&state, digest); 21 | 22 | for (i = 0; i < 16; i++) 23 | sprintf(&outString[i * 2], "%02x", digest[i]); 24 | } 25 | 26 | void say(const char *pName, char *pMessage) { 27 | time_t the_time; 28 | struct tm *the_localtime; 29 | char timestamp[256]; 30 | 31 | the_time = time(NULL); 32 | 33 | the_localtime = localtime(&the_time); 34 | 35 | strftime(timestamp, 255, "%H:%M:%S", the_localtime); 36 | 37 | printf("%s @ %s: %s\n", pName, timestamp, pMessage); 38 | 39 | } 40 | 41 | 42 | int release_semaphore(const char *pName, sem_t *pSemaphore, int live_dangerously) { 43 | int rc = 0; 44 | char s[1024]; 45 | 46 | say(pName, "Releasing the semaphore."); 47 | 48 | if (!live_dangerously) { 49 | rc = sem_post(pSemaphore); 50 | if (rc) { 51 | sprintf(s, "Releasing the semaphore failed; errno is %d\n", errno); 52 | say(pName, s); 53 | } 54 | } 55 | 56 | return rc; 57 | } 58 | 59 | 60 | int acquire_semaphore(const char *pName, sem_t *pSemaphore, int live_dangerously) { 61 | int rc = 0; 62 | char s[1024]; 63 | 64 | say(pName, "Waiting to acquire the semaphore."); 65 | 66 | if (!live_dangerously) { 67 | rc = sem_wait(pSemaphore); 68 | if (rc) { 69 | sprintf(s, "Acquiring the semaphore failed; errno is %d\n", errno); 70 | say(pName, s); 71 | } 72 | } 73 | 74 | return rc; 75 | } 76 | 77 | 78 | void read_params(struct param_struct *params) { 79 | char line[1024]; 80 | char name[1024]; 81 | char value[1024]; 82 | 83 | FILE *fp; 84 | 85 | fp = fopen("params.txt", "r"); 86 | 87 | while (fgets(line, 1024, fp)) { 88 | if (strlen(line) && ('#' == line[0])) 89 | ; // comment in input, ignore 90 | else { 91 | sscanf(line, "%[ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghjiklmnopqrstuvwxyz]=%s\n", name, value); 92 | 93 | // printf("name = %s, value = %d\n", name, value); 94 | 95 | if (!strcmp(name, "ITERATIONS")) 96 | params->iterations = atoi(value); 97 | if (!strcmp(name, "LIVE_DANGEROUSLY")) 98 | params->live_dangerously = atoi(value); 99 | if (!strcmp(name, "SEMAPHORE_NAME")) 100 | strcpy(params->semaphore_name, value); 101 | if (!strcmp(name, "SHARED_MEMORY_NAME")) 102 | strcpy(params->shared_memory_name, value); 103 | if (!strcmp(name, "PERMISSIONS")) 104 | params->permissions = (int)strtol(value, NULL, 8); 105 | if (!strcmp(name, "SHM_SIZE")) 106 | params->size = atoi(value); 107 | 108 | name[0] = '\0'; 109 | value[0] = '\0'; 110 | } 111 | } 112 | 113 | printf("iterations = %d\n", params->iterations); 114 | printf("danger = %d\n", params->live_dangerously); 115 | printf("semaphore name = %s\n", params->semaphore_name); 116 | printf("shared memory name = %s\n", params->shared_memory_name); 117 | printf("permissions = %o\n", params->permissions); 118 | printf("size = %d\n", params->size); 119 | } 120 | -------------------------------------------------------------------------------- /demos/demo1/utils.h: -------------------------------------------------------------------------------- 1 | struct param_struct { 2 | int iterations; 3 | int live_dangerously; 4 | char semaphore_name[512]; 5 | char shared_memory_name[512]; 6 | int permissions; 7 | int size; 8 | }; 9 | 10 | 11 | void md5ify(char *, char *); 12 | void say(const char *, char *); 13 | int acquire_semaphore(const char *, sem_t *, int); 14 | int release_semaphore(const char *, sem_t *, int); 15 | void read_params(struct param_struct *); 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /demos/demo1/utils.py: -------------------------------------------------------------------------------- 1 | import time 2 | import sys 3 | 4 | NULL_CHAR = 0 5 | 6 | 7 | def say(s): 8 | """Prints a timestamped, self-identified message""" 9 | who = sys.argv[0] 10 | if who.endswith(".py"): 11 | who = who[:-3] 12 | 13 | s = "%s@%1.6f: %s" % (who, time.time(), s) 14 | print(s) 15 | 16 | 17 | def write_to_memory(mapfile, s): 18 | """Writes the string s to the mapfile""" 19 | say("writing %s " % s) 20 | mapfile.seek(0) 21 | # I append a trailing NULL in case I'm communicating with a C program. 22 | s += '\0' 23 | s = s.encode() 24 | mapfile.write(s) 25 | 26 | 27 | def read_from_memory(mapfile): 28 | """Reads a string from the mapfile and returns that string""" 29 | mapfile.seek(0) 30 | s = [] 31 | c = mapfile.read_byte() 32 | while c != NULL_CHAR: 33 | s.append(c) 34 | c = mapfile.read_byte() 35 | 36 | s = ''.join([chr(c) for c in s]) 37 | 38 | say("read %s" % s) 39 | 40 | return s 41 | 42 | 43 | def read_params(): 44 | """Reads the contents of params.txt and returns them as a dict""" 45 | params = {} 46 | 47 | f = open("params.txt") 48 | 49 | for line in f: 50 | line = line.strip() 51 | if line: 52 | if line.startswith('#'): 53 | pass # comment in input, ignore 54 | else: 55 | name, value = line.split('=') 56 | name = name.upper().strip() 57 | 58 | if name == "PERMISSIONS": 59 | # Think octal, young man! 60 | value = int(value, 8) 61 | elif "NAME" in name: 62 | # This is a string; leave it alone. 63 | pass 64 | else: 65 | value = int(value) 66 | 67 | # print "name = %s, value = %d" % (name, value) 68 | 69 | params[name] = value 70 | 71 | f.close() 72 | 73 | return params 74 | -------------------------------------------------------------------------------- /demos/demo2/ReadMe.md: -------------------------------------------------------------------------------- 1 | This demonstrates use of message queues via two applications named after 2 | Mrs. Premise and Mrs. Conclusion of the Monty Python sketch. 3 | 4 | Like those two characters, these programs chat back and forth and the result 5 | is a lot of nonsense. In this case, what the programs are saying isn't the 6 | interesting part. What's interesting is how they're doing it. 7 | 8 | Mrs. Premise and Mrs. Conclusion (the programs, not the sketch characters) 9 | communicate with POSIX message queues. 10 | 11 | Mrs. Premise starts things off by creating the queue and sending a random 12 | string (the current time) to it. She then sits in a loop receiving whatever 13 | message is on the queue. If it is the same message she wrote, she sends it 14 | back to the queue. If it is a new message, it must be from Mrs. Conclusion. 15 | 16 | Meanwhile, Mrs. Conclusion is doing exactly the same thing, except that she 17 | assumes Mrs. Premise will write the first message. 18 | 19 | When either of these programs receives a new message, they send back an 20 | md5 hash of that message. This serves two purposes. First, it ensures that 21 | subsequent messages are very different so that if a message somehow gets 22 | corrupted (say by being partially overwritten by the next message), it will 23 | not escape notice. Second, it ensures that corruption can be detected if 24 | it happens, because Mrs. Premise and Mrs. Conclusion can calculate what the 25 | other's response to their message should be. 26 | 27 | Since message queues manage all of the concurrency issues transparently, 28 | Mrs. Premise and Mrs. Conclusion won't ever find their messages corrupted 29 | no matter how many messages they exchange. You can experiment with this by 30 | setting ITERATIONS in params.txt to a very large value. 31 | 32 | These programs are not meant as a demostration on how to make best use of a 33 | message queue. In fact, they're very badly behaved because they poll the 34 | queue as fast as possible -- they'll send your CPU usage right up to 100%. 35 | Remember, they're trying as hard as they can to step one another so as to 36 | expose any concurrency problems that might be present. 37 | 38 | Real code would want to sleep (or do something useful) in between calling 39 | send() and receive(). 40 | -------------------------------------------------------------------------------- /demos/demo2/SampleIpcConversation.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/osvenskan/posix_ipc/e0ee0128534cfd774ff3091ad7f068264661d896/demos/demo2/SampleIpcConversation.png -------------------------------------------------------------------------------- /demos/demo2/cleanup.py: -------------------------------------------------------------------------------- 1 | import posix_ipc 2 | import utils 3 | 4 | params = utils.read_params() 5 | 6 | try: 7 | posix_ipc.unlink_message_queue(params["MESSAGE_QUEUE_NAME"]) 8 | s = "message queue %s removed" % params["MESSAGE_QUEUE_NAME"] 9 | print(s) 10 | except: 11 | print("queue doesn't need cleanup") 12 | 13 | print("\nAll clean!") 14 | -------------------------------------------------------------------------------- /demos/demo2/conclusion.py: -------------------------------------------------------------------------------- 1 | # Python modules 2 | import hashlib 3 | 4 | # 3rd party modules 5 | import posix_ipc 6 | 7 | # Utils for this demo 8 | import utils 9 | 10 | 11 | utils.say("Oooo 'ello, I'm Mrs. Conclusion!") 12 | 13 | params = utils.read_params() 14 | 15 | # Mrs. Premise has already created the message queue. I just need a handle 16 | # to it. 17 | mq = posix_ipc.MessageQueue(params["MESSAGE_QUEUE_NAME"]) 18 | 19 | what_i_sent = "" 20 | 21 | for i in range(0, params["ITERATIONS"]): 22 | utils.say("iteration %d" % i) 23 | 24 | s, _ = mq.receive() 25 | s = s.decode() 26 | utils.say("Received %s" % s) 27 | 28 | while s == what_i_sent: 29 | # Nothing new; give Mrs. Premise another chance to respond. 30 | mq.send(s) 31 | 32 | s, _ = mq.receive() 33 | s = s.decode() 34 | utils.say("Received %s" % s) 35 | 36 | if what_i_sent: 37 | what_i_sent = what_i_sent.encode() 38 | try: 39 | assert(s == hashlib.md5(what_i_sent).hexdigest()) 40 | except AssertionError: 41 | raise AssertionError("Message corruption after %d iterations." % i) 42 | # else: 43 | # When what_i_sent is blank, this is the first message which 44 | # I always accept without question. 45 | 46 | # MD5 the reply and write back to Mrs. Premise. 47 | s = hashlib.md5(s.encode()).hexdigest() 48 | utils.say("Sending %s" % s) 49 | mq.send(s) 50 | what_i_sent = s 51 | 52 | 53 | utils.say("") 54 | utils.say("%d iterations complete" % (i + 1)) 55 | -------------------------------------------------------------------------------- /demos/demo2/params.txt: -------------------------------------------------------------------------------- 1 | # These parameters control how Mrs. Premise and Mrs. Conclusion behave. 2 | 3 | ITERATIONS=1000 4 | MESSAGE_QUEUE_NAME=/my_message_queue 5 | -------------------------------------------------------------------------------- /demos/demo2/premise.py: -------------------------------------------------------------------------------- 1 | # Python modules 2 | import time 3 | import hashlib 4 | 5 | # 3rd party modules 6 | import posix_ipc 7 | 8 | # Utils for this demo 9 | import utils 10 | 11 | 12 | utils.say("Oooo 'ello, I'm Mrs. Premise!") 13 | 14 | params = utils.read_params() 15 | 16 | # Create the message queue. 17 | mq = posix_ipc.MessageQueue(params["MESSAGE_QUEUE_NAME"], posix_ipc.O_CREX) 18 | 19 | # The first message is a random string (the current time). 20 | s = time.asctime() 21 | utils.say("Sending %s" % s) 22 | mq.send(s) 23 | what_i_sent = s 24 | 25 | for i in range(0, params["ITERATIONS"]): 26 | utils.say("iteration %d" % i) 27 | 28 | s, _ = mq.receive() 29 | s = s.decode() 30 | utils.say("Received %s" % s) 31 | 32 | # If the message is what I wrote, put it back on the queue. 33 | while s == what_i_sent: 34 | # Nothing new; give Mrs. Conclusion another chance to respond. 35 | mq.send(s) 36 | 37 | s, _ = mq.receive() 38 | s = s.decode() 39 | utils.say("Received %s" % s) 40 | 41 | # What I read must be the md5 of what I wrote or something's 42 | # gone wrong. 43 | what_i_sent = what_i_sent.encode() 44 | 45 | try: 46 | assert(s == hashlib.md5(what_i_sent).hexdigest()) 47 | except AssertionError: 48 | raise AssertionError("Message corruption after %d iterations." % i) 49 | 50 | # MD5 the reply and write back to Mrs. Conclusion. 51 | s = hashlib.md5(s.encode()).hexdigest() 52 | utils.say("Sending %s" % s) 53 | mq.send(s) 54 | what_i_sent = s 55 | 56 | utils.say("") 57 | utils.say("%d iterations complete" % (i + 1)) 58 | 59 | utils.say("Destroying the message queue.") 60 | mq.close() 61 | # I could call simply mq.unlink() here but in order to demonstrate 62 | # unlinking at the module level I'll do it that way. 63 | posix_ipc.unlink_message_queue(params["MESSAGE_QUEUE_NAME"]) 64 | -------------------------------------------------------------------------------- /demos/demo2/utils.py: -------------------------------------------------------------------------------- 1 | import time 2 | import sys 3 | 4 | 5 | def say(s): 6 | who = sys.argv[0] 7 | if who.endswith(".py"): 8 | who = who[:-3] 9 | 10 | s = "%s@%1.6f: %s" % (who, time.time(), s) 11 | print(s) 12 | 13 | 14 | def read_params(): 15 | params = {} 16 | 17 | f = open("params.txt") 18 | 19 | for line in f: 20 | line = line.strip() 21 | if len(line): 22 | if line.startswith('#'): 23 | pass # comment in input, ignore 24 | else: 25 | name, value = line.split('=') 26 | name = name.upper().strip() 27 | 28 | if name == "PERMISSIONS": 29 | value = int(value, 8) 30 | elif "NAME" in name: 31 | # This is a string; leave it alone. 32 | pass 33 | else: 34 | value = int(value) 35 | 36 | # print("name = %s, value = %d" % (name, value)) 37 | 38 | params[name] = value 39 | 40 | f.close() 41 | 42 | return params 43 | -------------------------------------------------------------------------------- /demos/demo3/ReadMe.md: -------------------------------------------------------------------------------- 1 | These scripts demonstrate four message queue notification techniques. 2 | 3 | All of demos ask you to enter a message. That message is then sent to the 4 | queue and received in a notification handler and printed to stdout. 5 | 6 | - one_shot_signal.py and one_shot_thread.py receive their notifications via a 7 | signal and thread, respectively. After one message & notification, the demo 8 | exits. 9 | 10 | - repeating_signal.py and repeating_thread.py are similar, except that they 11 | re-register for notifications in their notification handler so you can 12 | enter as many messages as you like. 13 | -------------------------------------------------------------------------------- /demos/demo3/cleanup.py: -------------------------------------------------------------------------------- 1 | import posix_ipc 2 | import utils 3 | 4 | try: 5 | posix_ipc.unlink_message_queue(utils.QUEUE_NAME) 6 | s = "message queue %s removed" % utils.QUEUE_NAME 7 | print(s) 8 | except: 9 | print("queue doesn't need cleanup") 10 | 11 | print("\nAll clean!") 12 | -------------------------------------------------------------------------------- /demos/demo3/one_shot_signal.py: -------------------------------------------------------------------------------- 1 | # Python modules 2 | import time 3 | import signal 4 | 5 | # 3rd party modules 6 | import posix_ipc 7 | 8 | # Utils for this demo 9 | import utils 10 | 11 | 12 | MY_SIGNAL = signal.SIGUSR1 13 | 14 | 15 | def handle_signal(signal_number, stack_frame): 16 | message, priority = mq.receive() 17 | 18 | print("Ding! Message with priority %d received: %s" % (priority, message)) 19 | 20 | 21 | # Create the message queue. 22 | mq = posix_ipc.MessageQueue(utils.QUEUE_NAME, posix_ipc.O_CREX) 23 | 24 | # Request notifications 25 | mq.request_notification(MY_SIGNAL) 26 | 27 | # Register my signal handler 28 | signal.signal(MY_SIGNAL, handle_signal) 29 | 30 | # Get user input and send it to the queue. 31 | print("Enter a message:") 32 | mq.send(input()) 33 | 34 | # The signal fires almost instantly, but if I don't pause at least 35 | # briefly then the main thread may exit before the notification fires. 36 | print("Sleeping for one second to allow the notification to happen.") 37 | time.sleep(1) 38 | 39 | print("Destroying the message queue.") 40 | mq.close() 41 | # I could call simply mq.unlink() here but in order to demonstrate 42 | # unlinking at the module level I'll do it that way. 43 | posix_ipc.unlink_message_queue(utils.QUEUE_NAME) 44 | -------------------------------------------------------------------------------- /demos/demo3/one_shot_thread.py: -------------------------------------------------------------------------------- 1 | # Python modules 2 | import time 3 | 4 | # 3rd party modules 5 | import posix_ipc 6 | 7 | # Utils for this demo 8 | import utils 9 | 10 | 11 | def process_notification(mq): 12 | message, priority = mq.receive() 13 | 14 | print("Ding! Message with priority %d received: %s" % (priority, message)) 15 | 16 | 17 | # Create the message queue. 18 | mq = posix_ipc.MessageQueue(utils.QUEUE_NAME, posix_ipc.O_CREX) 19 | 20 | # Request notifications 21 | mq.request_notification((process_notification, mq)) 22 | 23 | # Get user input and send it to the queue. 24 | print("Enter a message:") 25 | mq.send(input()) 26 | 27 | # The callback happens almost instantly, but if I don't pause at least 28 | # briefly then the main thread may exit before the notification fires. 29 | print("Sleeping for one second to allow the notification to happen.") 30 | time.sleep(1) 31 | 32 | print("Destroying the message queue.") 33 | mq.close() 34 | # I could call simply mq.unlink() here but in order to demonstrate 35 | # unlinking at the module level I'll do it that way. 36 | posix_ipc.unlink_message_queue(utils.QUEUE_NAME) 37 | -------------------------------------------------------------------------------- /demos/demo3/repeating_signal.py: -------------------------------------------------------------------------------- 1 | # Python modules 2 | import signal 3 | 4 | # 3rd party modules 5 | import posix_ipc 6 | 7 | # Utils for this demo 8 | import utils 9 | 10 | 11 | MY_SIGNAL = signal.SIGUSR1 12 | 13 | 14 | def handle_signal(signal_number, stack_frame): 15 | message, priority = mq.receive() 16 | 17 | print("Ding! Message with priority %d received: %s" % (priority, message)) 18 | 19 | # Re-register for notifications 20 | mq.request_notification(MY_SIGNAL) 21 | 22 | 23 | # Create the message queue. 24 | mq = posix_ipc.MessageQueue(utils.QUEUE_NAME, posix_ipc.O_CREX) 25 | 26 | # Request notifications 27 | mq.request_notification(MY_SIGNAL) 28 | 29 | # Register my signal handler 30 | signal.signal(MY_SIGNAL, handle_signal) 31 | 32 | # Get user input and send it to the queue. 33 | msg = "42" 34 | while msg: 35 | print("\nEnter a message. A blank message will end the demo:") 36 | msg = input() 37 | if msg: 38 | mq.send(msg) 39 | 40 | print("Destroying the message queue.") 41 | mq.close() 42 | # I could call simply mq.unlink() here but in order to demonstrate 43 | # unlinking at the module level I'll do it that way. 44 | posix_ipc.unlink_message_queue(utils.QUEUE_NAME) 45 | -------------------------------------------------------------------------------- /demos/demo3/repeating_thread.py: -------------------------------------------------------------------------------- 1 | # Python modules 2 | 3 | # 3rd party modules 4 | import posix_ipc 5 | 6 | # Utils for this demo 7 | import utils 8 | 9 | 10 | def process_notification(mq): 11 | message, priority = mq.receive() 12 | 13 | print("Ding! Message with priority %d received: %s" % (priority, message)) 14 | 15 | # Re-register for notifications 16 | mq.request_notification((process_notification, mq)) 17 | 18 | 19 | # Create the message queue. 20 | mq = posix_ipc.MessageQueue(utils.QUEUE_NAME, posix_ipc.O_CREX) 21 | 22 | # Request notifications 23 | mq.request_notification((process_notification, mq)) 24 | 25 | # Get user input and send it to the queue. 26 | s = "42" 27 | while s: 28 | print("\nEnter a message. A blank message will end the demo:") 29 | s = input() 30 | if s: 31 | mq.send(s) 32 | 33 | print("Destroying the message queue.") 34 | mq.close() 35 | # I could call simply mq.unlink() here but in order to demonstrate 36 | # unlinking at the module level I'll do it that way. 37 | posix_ipc.unlink_message_queue(utils.QUEUE_NAME) 38 | -------------------------------------------------------------------------------- /demos/demo3/utils.py: -------------------------------------------------------------------------------- 1 | QUEUE_NAME = "/my_message_queue" 2 | -------------------------------------------------------------------------------- /demos/demo4/ReadMe.md: -------------------------------------------------------------------------------- 1 | This demonstrates the use of a semaphore with a Python context manager (a 2 | 'with' statement). 3 | 4 | To run the demo, simply run `python parent.py`. It launches child.py. 5 | 6 | The programs parent.py and child.py share a semaphore; the latter acquires the 7 | semaphore via a context manager. The child process deliberately kills itself via 8 | an error about half the time (randomly) in order to demonstrate that the 9 | context manager releases the semaphore regardless of whether or not the context 10 | block is exited gracefully. 11 | 12 | Once the child releases the semaphore, the parent destroys it. 13 | 14 | The whole thing happens in less than 10 seconds. 15 | 16 | -------------------------------------------------------------------------------- /demos/demo4/child.py: -------------------------------------------------------------------------------- 1 | import posix_ipc 2 | import time 3 | import sys 4 | import random 5 | 6 | # The parent passes the semaphore's name to me. 7 | name = sys.argv[1] 8 | 9 | print('Child: waiting to aquire semaphore ' + name) 10 | 11 | with posix_ipc.Semaphore(name) as sem: 12 | print('Child: semaphore ' + sem.name + ' aquired; holding for 3 seconds.') 13 | 14 | # Flip a coin to determine whether or not to bail out of the context. 15 | if random.randint(0, 1): 16 | print("Child: raising ValueError to demonstrate unplanned context exit") 17 | raise ValueError 18 | 19 | time.sleep(3) 20 | 21 | print('Child: gracefully exiting context (releasing the semaphore).') 22 | -------------------------------------------------------------------------------- /demos/demo4/parent.py: -------------------------------------------------------------------------------- 1 | import subprocess 2 | import posix_ipc 3 | import time 4 | import os 5 | 6 | sem = posix_ipc.Semaphore(None, posix_ipc.O_CREX, initial_value=1) 7 | print("Parent: created semaphore {}.".format(sem.name)) 8 | 9 | sem.acquire() 10 | 11 | # Spawn a child that will wait on this semaphore. 12 | path, _ = os.path.split(__file__) 13 | print("Parent: spawning child process...") 14 | subprocess.Popen(["python", os.path.join(path, 'child.py'), sem.name]) 15 | 16 | for i in range(3, 0, -1): 17 | print("Parent: child process will acquire the semaphore in {} seconds...".format(i)) 18 | time.sleep(1) 19 | 20 | sem.release() 21 | 22 | # Sleep for a second to give the child a chance to acquire the semaphore. 23 | # This technique is a little sloppy because technically the child could still 24 | # starve, but it's certainly sufficient for this demo. 25 | time.sleep(1) 26 | 27 | # Wait for the child to release the semaphore. 28 | print("Parent: waiting for the child to release the semaphore.") 29 | sem.acquire() 30 | 31 | # Clean up. 32 | print("Parent: destroying the semaphore.") 33 | sem.release() 34 | sem.unlink() 35 | 36 | msg = """ 37 | By the time you're done reading this, the parent will have exited and so the 38 | operating system will have destroyed the semaphore. You can prove that the 39 | semaphore is gone by running this command and observing that it raises 40 | posix_ipc.ExistentialError -- 41 | 42 | python -c "import posix_ipc; posix_ipc.Semaphore('{}')" 43 | 44 | """.format(sem.name) 45 | 46 | print(msg) 47 | -------------------------------------------------------------------------------- /demos/demo5/ReadMe.md: -------------------------------------------------------------------------------- 1 | Demonstration of using message queues together with the `selectors` module in Python 2 | === 3 | 4 | The [`selectors` module](https://docs.python.org/3/library/selectors.html) 5 | provides high-level I/O multiplexing akin to an event library -- in brief, 6 | various event sources, such as file handlers, sockets, message queues, can be 7 | registered with an event selector, which then returns if any event is 8 | triggered. 9 | 10 | `selectors` has been part of the Python standard library since Python 3.4. 11 | The low-level functionality is OS specific and more information is available in 12 | [the Python documentation for `selectors`](https://docs.python.org/3/library/selectors.html). 13 | 14 | The example consists of two Python files: `listener.py` and `tranmitter.py`. 15 | 16 | The listener creates a message queues and waits for events using the 17 | `selectors` module. When the transmitter program is run, a message is sent 18 | and the listener will print the content. 19 | 20 | Procedure: 21 | 22 | 1. Run `listener.py` 23 | 2. Run `transmitter.py` (in a new terminal) 24 | 3. Observe that listener has printed and exited 25 | 26 | Note that `listener.py` MUST be run first. 27 | -------------------------------------------------------------------------------- /demos/demo5/listener.py: -------------------------------------------------------------------------------- 1 | import posix_ipc 2 | import selectors 3 | 4 | # This program uses `posix_ipc` together with the `selectors`library from the 5 | # Python standard library. `selectors` provides "high-level I/O multiplexing" akin to having an 6 | # event library. 7 | 8 | # The message queue is created as usual 9 | mq = posix_ipc.MessageQueue("/python_ipc_test", flags=posix_ipc.O_CREAT) 10 | mq.block = False 11 | 12 | # Function is defined to handle events on the queue 13 | 14 | def accept(message_queue, mask): 15 | (msg, prio) = message_queue.receive() 16 | print("Message: ", msg) 17 | print("Priority: ", prio) 18 | 19 | # The selector can now be created... 20 | 21 | sel = selectors.DefaultSelector() 22 | 23 | # ... and the message queue is registered. Other event sources could also be 24 | # registered simultaneously, but for now we stick to the queue 25 | 26 | sel.register(mq, selectors.EVENT_READ, accept) 27 | 28 | # `.select()` will block until an event is triggered 29 | 30 | print("Listening...") 31 | 32 | events = sel.select() 33 | for key, mask in events: 34 | # `.data` contains the third argument from `.register` above -- we use it for the callback. 35 | callback = key.data 36 | callback(key.fileobj, mask) 37 | 38 | # With the message successfully received, we can unlink and close. 39 | 40 | mq.unlink() 41 | mq.close() 42 | -------------------------------------------------------------------------------- /demos/demo5/transmitter.py: -------------------------------------------------------------------------------- 1 | import posix_ipc 2 | 3 | # This program opens the message queue and sends a message 4 | 5 | mq = posix_ipc.MessageQueue("/python_ipc_test") 6 | mq.block = False 7 | 8 | mq.send("From transmitter") 9 | -------------------------------------------------------------------------------- /history.md: -------------------------------------------------------------------------------- 1 | # The posix_ipc Module for POSIX IPC Under Python -- Version History 2 | 3 | This is the version history for the [posix_ipc module](https://github.com/osvenskan/posix_ipc). 4 | 5 | As of version 1.0.0, I consider this module complete. I will continue to support it and look for useful features to add, but right now I don't see any. 6 | 7 | - **Current – 1.2.0 (16 April 2025) –** 8 | 9 | This release modernizes the file layout, building, and packaging of `posix_ipc`. There are no changes to the core code. 10 | 11 | - Thanks to [Matiiss](https://github.com/Matiiss), this version integrates with `cibuildwheel` to automate testing and create wheels for many platforms. 12 | - The `discover_system_info.py` script (formerly `prober.py`) now respects the `CC` environment variable instead of using a hardcoded `cc` to launch the compiler. Thanks to [György Sárvári](https://github.com/OldManYellsAtCloud) for the patch. 13 | - Added `building.md` to begin documenting `discover_system_info.py` and the header file it creates. 14 | - Modernized the project to use `pyproject.toml` and modern build practices (e.g. `python -m build`). I reorganized the project's files to make it easier to use `pyproject.toml`. 15 | 16 | - 1.1.1 (31 December 2022) – 17 | 18 | - Fixed a bug introduced in 1.1.0 where setup would fail on systems where [the default file system encoding is not UTF-8](https://github.com/osvenskan/posix_ipc/issues/40). 19 | - Made message queue tests more conservative to avoid resource exhaustion that [could occur on a system with an atypical configuration](https://github.com/osvenskan/posix_ipc/issues/42). 20 | - Fixed (again) [a bug related to shared memory size](https://github.com/osvenskan/posix_ipc/issues/35), this time for real! Thanks to [Rudolf Hornig](https://github.com/rhornig) for the fix. 21 | 22 | - 1.1.0 (25 November 2022) – 23 | 24 | - Drop support for Python 2.7 and ≤ 3.6. 25 | - Converted doc to Markdown. Thanks to [Bahram Aghaei](https://github.com/GreatBahram) and [Chris Kitras](https://github.com/christopolise). 26 | - On macOS, relax a test related to shared memory size (https://github.com/osvenskan/posix_ipc/issues/35). 27 | 28 | - 1.0.5 (10 October 2020) – 29 | 30 | This is the last version that will support Python 2.7. 31 | 32 | - Added a `fileno` method to `SharedMemory` and `MessageQueue` objects to support Python's `selectors` standard library module. 33 | - Added a demo (in the demo5 directory) demonstrating use of `selectors`. *Mange tak* to [Henrik Enggaard](https://github.com/henrikh) for the `fileno()` suggestion and for contributing the demo. 34 | - Added automatic Travis testing on GitHub for Python 3.7 thanks to Ben Harper, and for Python 2.7 under macOS. 35 | - Fixed a [deprecation warning under Python 3.9](https://github.com/osvenskan/posix_ipc/issues/22). *Merci* to sbraz for the bug report. 36 | - Updated HTML documentation (including this file) to HTML 5. 37 | 38 | - 1.0.4 (13 Feb 2018) – 39 | - Fixed bug where the `SharedMemory` and `Semaphore` classes [didn't behave correctly](https://github.com/osvenskan/posix_ipc/issues/2) when assigned a file descriptor of 0. Thanks to Thomas Troeger for the bug report. 40 | - Fixed a small but [significant documentation bug](https://github.com/osvenskan/posix_ipc/issues/3) which stated that the `size` param was ignored when opening an existing `SharedMemory` segment. Thanks to Thomas Troeger, again. 41 | - Fixed a compilation failure under Python 3 when the internal use `POSIX_IPC_DEBUG` flag was on. Děkuji to Tomáš Zahradnický for the bug report. 42 | 43 | - 1.0.3 (10 Jan 2018) – 44 | 45 | A *mea culpa* release to clean up some accumulated technical debt. 46 | 47 | - Moved repository to git and GitHub. 48 | - Changed link options in the build script for the first demo that caused a build fail on some (all?) Linuxes. 49 | - Fix a few bugs where functions that were documented to accept keyword arguments only accepted them as positional arguments; added tests to exercise keyword arguments. 50 | - Removed dependency on `setuptools` from `setup.py`. 51 | - Added code to semaphore tests to avoid triggering a bug on older FreeBSDs (and perhaps other BSDs). 52 | - PEP8 improvements. 53 | 54 | - 1.0.2 (10 Jan 2018) – 55 | 56 | This version was also skipped due to a release error. Those responsible for sacking the people who have just been sacked, have been sacked. 57 | 58 | - 1.0.1 (10 Jan 2018) – 59 | 60 | This version was skipped due to a release error. Those responsible have been sacked. 61 | 62 | - 1.0.0 (11 Mar 2015) – 63 | - Added ability to pass names as unicode in Python 2. 64 | - Added ability to pass names as bytes in Python 3. 65 | - Dropped support for Python < 2.7. 66 | - Made unit tests nicer by taking advantage of Python 2.7+ certainty and removed some code that only supported Python 2.6. 67 | - 0.9.9 (14 Nov 2014) – 68 | - Added the ability to build on platforms that don't support the POSIX Realtime Signals Extension. ありがとう to Takashi Yamamoto for the patch. 69 | - Added extensive unit tests. 70 | - Minor documentation updates. 71 | - 0.9.8 (20 Feb 2014) – 72 | 73 | As with 0.9.7, there are no code or feature changes in this version. This version merely corrects a documentation error. 74 | 75 | This version comes with a big wish for peace in Ukraine. Мир! 76 | 77 | - 0.9.7 (20 Feb 2014) – 78 | 79 | There are no code or feature changes in this version. The bump in version number reflects that this is the first version also available on PyPI. 80 | 81 | This version comes with a big wish for peace in Ukraine. Мир! 82 | 83 | - 0.9.6 (23 Oct 2013) – 84 | 85 | Fixed two BSD-specific bugs introduced in version 0.9.5 that occurred if the kernel module `mqueuefs` wasn't loaded at install time. Specifically -- 86 | 87 | - The installer would print a harmless but distracting error message from sysctl. (This also affected OS X which is FreeBSD-ish.) 88 | - `posix_ipc` would build with an inappropriate value for `QUEUE_MESSAGES_MAX_DEFAULT`. Subsequent attempts to create a message queue would fail unless the caller set the `max_messages` param to an appropriate value. (This didn't affect OS X since OS X doesn't support message queues at all.) 89 | 90 | Also, rewrote the message queue thread notification code to address the old bug (`Fatal Python error: PyEval_AcquireLock: current thread state is NULL`) that appeared during release testing for 0.9.5 and which has plagued me on and off since I wrote this code. The new code uses [the algorithm recommended in the Python documentation](http://docs.python.org/2/c-api/init.html#non-python-created-threads) which may have been flaky when I started using it in Python 2.4. It seems stable now under Python 2.6+/3. 91 | 92 | - 0.9.5 (14 Oct 2013) – 93 | - Added the ability to use Semaphores in context managers. Thanks to Matt Ruffalo for the suggestion and patch. 94 | - Fixed a big under FreeBSD 9.x where I used overly ambitious values for some message queue constants at build time. Now, `posix_ipc` asks `sysctl` for the correct values. *Köszönöm* to Attila Nagy for the bug report. 95 | - 0.9.4 (2 Sept 2012) – 96 | 97 | Fixed a buglet. When creating shared memory under Linux and specifying both a size and the read-only flag, creating the memory would succeed but calling `ftruncate()` would fail. The failure to change the size was correctly reported but `posix_ipc` failed to clean up the shared memory segment it had created. That's now fixed. Thanks to Kevin Miles for the bug report. 98 | 99 | - 0.9.3 (2 Jan 2012) – 100 | 101 | Added a bugfix/feature to raise an error (rather than segault) when trying to use a closed semaphore. Thanks to Russel for the suggestion and patch. 102 | 103 | - 0.9.2 (6 Nov 2011) – 104 | - Fixed a bug where timeouts in `Semaphore.acquire()`, `MessageQueue.send()` and `MessageQueue.receive()` were only accurate to about one second due to use of the C call `time()`. Switching to `gettimeofday()` fixes the problem. Thanks to Douglas Young for the bug report and patch. 105 | - Fixed a bug in `prober.py` that caused install to fail under Ubuntu 11.10. `prober.py` specified link options in the wrong order, and so linking one of the test applications that's built during setup was failing. Thanks to Kevin Miles for the bug report. 106 | - Added a check in `prober.py` to see if `sysconf_names` exists in the `os` module. It doesn't exist under Cygwin, and this code caused an error on that platform. Thanks to Rizwan Raza for the bug report. 107 | - 0.9.1 (7 Apr 2011) – 108 | - Fixed (?) a bug in message queue thread notification that caused `ceval: tstate mix-up` and other fun messages. Thanks to Lev Maximov for the bug report. 109 | - Added the `demo3` directory with demos of message queue. This was supposed be included in version 0.9.0 but I accidentally left it out. (Also reported by Lev.) 110 | - 0.9.0 (31 Dec 2010) – 111 | 112 | Added the `demo3` directory with demos of message queue notification techniques. Also, fixed two bugs related to message queue notification. Big thanks to Philip D. Bober for debugging and providing a patch to the most difficult part of the code. The bugs were – 113 | 114 | - First, the series of calls to set up the Python thread in `process_notification()` were simply wrong. They worked some (most?) of the time but would segfault eventually because I was creating a Python thread state when I should not have. 115 | - Second, the code in `process_notification()` failed to consider that the user's callback might re-request notification, thus overwriting pointers that I would later decref. `process_notification()` is now thread-safe. 116 | - 0.8.1 (15 Mar 2010) – 117 | 118 | Fixed a sloppy declaration that caused a compile error under Cygwin 1.7.1. Thanks to Jill McCutcheon for the bug report. 119 | 120 | - 0.8.0 (2 Mar 2010) – 121 | - Fixed message queue support detection in FreeBSD and the platform-specific documentation about FreeBSD. 122 | - Rearranged the documentation and split the history (which you're reading now) into a separate file. 123 | - I fixed two small bugs related to the confusing message queue constants. The bugs and associated changes are explained below. The explanation is really long not because the changes were big (they weren't), but because they and rationale behind them are subtle. 124 | 125 | Fixing these bugs was made easier by this realization: on all of the systems to which I have access that implement message queues (FreeBSD, OpenSolaris, Linux, and Windows + Cygwin), all except Linux implement them as memory-mapped files or something similar. On these non-Linux systems, the maximum queue message count and size are pretty darn big (`LONG_MAX`). Therefore, only on Linux is anyone likely to encounter limits to message queue size and content. 126 | 127 | The first bug I fixed was related to four message queue constants mentioned in `posix_ipc` documentation: `QUEUE_MESSAGES_MAX`, `QUEUE_MESSAGES_MAX_DEFAULT`, `QUEUE_MESSAGE_SIZE_MAX` and `QUEUE_MESSAGE_SIZE_MAX_DEFAULT`. All four were defined in the `C` code, but the two `XXX_DEFAULT` constants weren't exposed on the Python side. 128 | 129 | The second bug was that under Linux, `QUEUE_MESSAGES_MAX` and `QUEUE_MESSAGE_SIZE_MAX` were permanently fixed to their values at `posix_ipc`'s compile/install time even if the relevant system values changed later. Thanks to Kyle Tippetts for bringing this to my attention. 130 | 131 | `QUEUE_MESSAGES_MAX_DEFAULT` was arbitrarily limited to (at most) 1024. This wasn't a bug, just a bad choice. 132 | 133 | I made a few changes in order to fix these problems – 134 | 135 | 1. The constants `QUEUE_MESSAGES_MAX` and `QUEUE_MESSAGE_SIZE_MAX` **have been deleted** since they were only sure to be accurate on systems where they were irrelevant. Furthermore, Linux (the only place where they matter) exposes these values through the file system (in `/proc/sys/fs/mqueue/msg_max` and `/proc/sys/fs/mqueue/msgsize_max` respectively) so Python apps that need them can read them without any help from `posix_ipc`. 136 | 2. `QUEUE_MESSAGES_MAX_DEFAULT` and `QUEUE_MESSAGE_SIZE_MAX_DEFAULT` are now exposed to Python as they should have been all along. `QUEUE_MESSAGES_MAX_DEFAULT` is now set to `LONG_MAX` on all platforms except Linux, where it's set at compile time from `/proc/sys/fs/mqueue/msg_max`. 137 | 3. `QUEUE_MESSAGE_SIZE_MAX_DEFAULT` remains at the fairly arbitrary value of 8k. It's not a good idea to make it too big since a buffer of this size is allocated every time `MessageQueue.receive()` is called. Under Linux, I check the contents of `/proc/sys/fs/mqueue/msgsize_max` and make `QUEUE_MESSAGE_SIZE_MAX_DEFAULT` smaller if necessary. 138 | - 0.7.0 (21 Feb 2010) – 139 | 140 | Added Python 3.1 support. 141 | 142 | - 0.6.3 (15 Feb 2009) – 143 | - Fixed a bug where creating an IPC object with invalid parameters would correctly raise a `ValueError`, but with a message that may or may not have correctly identified the cause. (My code was making an educated guess that was sometimes wrong.) 144 | 145 | As of this version, if initialization of an IPC object fails with the error code `EINVAL`, `posix_ipc` raises a `ValueError` with the vague-but-correct message "Invalid parameter(s)". 146 | 147 | - Cleaned up the code a little internally. 148 | - 0.6.2 (30 Dec 2009) – 149 | 150 | Fixed a bug where a `MessageQueue`'s `mode` attribute returned garbage. *Grazie* to Stefano Debenedetti for the bug report. 151 | 152 | - 0.6.1 (29 Nov 2009) – 153 | 154 | There were no functional changes to the module in this version, but I added the convenience function `close_fd()` and fixed some docmentation and demo bugs/sloppiness. 155 | 156 | - Added the convenience function `SharedMemory.close_fd()`. Thanks to Kyle Tippetts for pointing out the usefulness of this. 157 | - Added the module attributes `__version__`, `__copyright__`, `__author__` and `__license__`. 158 | - Fixed the license info embedded in `posix_ipc_module.c` which was still referring to GPL. 159 | - Replaced `file()` in `setup.py` with `open()`/`close()`. 160 | - Demo changes – 161 | - Made the demo a bit faster, especially for large shared memory chunks. Thanks to Andrew Trevorrow for the suggestion and patch. 162 | - Fixed a bug in premise.c; it wasn't closing the semaphore. 163 | - Fixed a bug in premise.py; it wasn't closing the shared memory's file descriptor. 164 | - Fixed bugs in conclusion.py; it wasn't closing the shared memory's file descriptor, the semaphore or the mapfile. 165 | - 0.6 (5 Oct 2009) – 166 | - Relicensed from the GPL to a BSD license to celebrate the one year anniversary of this module. 167 | - Updated Cygwin info. 168 | - 0.5.5 (17 Sept 2009) – 169 | - Set `MQ_MAX_MESSAGES` and `MQ_MAX_MESSAGE_SIZE` to `LONG_MAX` under cygwin. (*Danke* to René Liebscher.) 170 | - Surrounded the `#define PAGE_SIZE` in probe_results.h with `#ifndef/#endif` because it is already defined on some systems. (*Danke* to René Liebscher, again.) 171 | - Minor documentation changes. 172 | - 0.5.4 (21 Jun 2009) – 173 | - Added SignalError. 174 | - Fixed a bug where [Python would generate an uncatchable KeyboardInterrupt when Ctrl-C was hit during a wait](http://groups.google.com/group/comp.lang.python/browse_thread/thread/ada39e984dfc3da6/fd6becbdce91a6be?#fd6becbdce91a6be) (e.g. `sem.acquire()`). 175 | 176 | Thanks to Maciek W. for reporting the problem and to Piet van Oostrum and Greg for help with a solution. 177 | 178 | - Minor documentation changes. 179 | - 0.5.3 (8 Mar 2009) – 180 | - Added automatic generation of names. 181 | - Changed status to beta. 182 | - 0.5.2 (12 Feb 2009) – 183 | - Fixed a memory leak in `MessageQueue.receive()`. 184 | - Fixed a bug where the name of the `MessageQueue` `current_messages` attribute didn't match the name given in the documentation. 185 | - Added the VERSION attribute to the module. 186 | - Fixed a documentation bug that said message queue notifications were not yet supported. 187 | - 0.5.1 (8 Feb 2009) – 188 | - Fixed outdated info in setup.py that was showing up in the Python package index. Updated README while I was at it. 189 | - 0.5 (8 Feb 2009) – 190 | - Added the message queue notification feature. 191 | - Added a `mode` attribute to each type. 192 | - Added `str()` and `repr()` support to each object. 193 | - Added a demo for message queues. 194 | - Fixed some minor documentation problems and added some information (esp. about Windows + Cygwin). 195 | - 0.4 (9 Jan 2009) – 196 | - Added message queue support. 197 | - Fixed the poor choices I'd made for names for classes and errors by removing the leading "Posix" and "PosixIpc". 198 | - Simplified the prober and expanded it (for message queue support). 199 | - Cleaned up this documentation. 200 | - 0.3.2 (4 Jan 2009) – 201 | - Fixed an uninitialized value passed to PyMem_Free() when invalid params were passed to either constructor. 202 | - 0.3.1 (1 Jan 2009) – 203 | - Fixed a big bug where the custom exceptions defined by this module weren't visible. 204 | - Fixed a compile complaint about the redefinition of `SEM_VALUE_MAX` on Linux (Ubuntu) that I introduced in the previous version. 205 | - Fixed a bug in the demo program premise.c where I wasn't closing the file descriptor associated with the shared memory. 206 | - Added the `PAGE_SIZE` attribute. This was already available in the mmap module that you need to use shared memory anyway, but adding it makes the interface more consistent with the `sysv_ipc` module. 207 | - 0.3 (19 Dec 2008) – 208 | - Added informative custom errors instead of raising OSError when something goes wrong. 209 | - Made the code friendly to multi-threaded applications. 210 | - Added the constants `O_CREX` and `SEMAPHORE_VALUE_MAX`. 211 | - Added code to prohibit negative timeout values. 212 | - 0.2 (4 Dec 2008) – 213 | - Removed the un-Pythonic `try_acquire()` method. The same functionality is now available by passing a timeout of `0` to the `.acquire()` method. 214 | - Renamed the module constant `ACQUIRE_TIMEOUT_SUPPORTED` to `SEMAPHORE_TIMEOUT_SUPPORTED`. 215 | - Moved the demo code into its own directory and added C versions of the Python scripts. The parameters are now in a text file shared by the Python and C program, so you can run the C version of Mrs. Premise and have it communicate with the Python version of Mrs. Conclusion and vice versa. 216 | - 0.1 (9 Oct 2008) – Original (alpha) version. -------------------------------------------------------------------------------- /memory_leak_tests.py: -------------------------------------------------------------------------------- 1 | # Python modules 2 | import gc 3 | import os 4 | import subprocess 5 | import random 6 | import re 7 | import sys 8 | 9 | # My module 10 | import posix_ipc 11 | 12 | # TEST_COUNT = 10 13 | TEST_COUNT = 1024 * 102 14 | 15 | SKIP_SEMAPHORE_TESTS = False 16 | SKIP_SHARED_MEMORY_TESTS = False 17 | SKIP_MESSAGE_QUEUE_TESTS = False 18 | 19 | if not posix_ipc.MESSAGE_QUEUES_SUPPORTED: 20 | SKIP_MESSAGE_QUEUE_TESTS = True 21 | 22 | # ps output looks like this: 23 | # RSZ VSZ 24 | # 944 75964 25 | ps_output_regex = re.compile(""" 26 | ^ 27 | \s* # whitespace before first heading 28 | \S* # first heading (e.g. RSZ) 29 | \s+ # whitespace between headings 30 | \S* # second heading (e.g VSZ) 31 | \s+ # newline and whitespace before first numeric value 32 | (\d+) # first value 33 | \s+ # whitespace between values 34 | (\d+) # second value 35 | \s* # trailing whitespace if any 36 | $ 37 | """, re.MULTILINE | re.VERBOSE) 38 | 39 | # On OS X, Ubuntu and OpenSolaris, both create/destroy tests show some growth 40 | # is rsz and vsz. (e.g. 3248 versus 3240 -- I guess these are measured 41 | # in kilobytes?) When I increased the number of iterations by a factor of 10, 42 | # the delta didn't change which makes me think it isn't an actual leak 43 | # but just some memory consumed under normal circumstances. 44 | 45 | # When I created an intentional leak by commenting out the call to 46 | # PyMem_Free(self->name); in Semaphore_dealloc(), here's what 47 | # happened to the memory usage: 48 | # Memory usage before, RSS = 3264, VSZ = 84116 49 | # Python's GC reports no leftover garbage 50 | # Memory usage after, RSS = 19912, VSZ = 100500 51 | 52 | 53 | NAME_CHARACTERS = "abcdefghijklmnopqrstuvwxyz" 54 | NAME_LENGTH = 10 55 | 56 | 57 | def say(s): 58 | """A wrapper for print() that's compatible with Python 2 & 3""" 59 | print(s) 60 | 61 | 62 | def random_string(length): 63 | return ''.join(random.sample("abcdefghijklmnopqrstuvwxyz", length)) 64 | 65 | 66 | def print_mem_before(): 67 | say("Memory usage before, RSS = %d, VSZ = %d" % get_memory_usage()) 68 | 69 | 70 | def print_mem_after(): 71 | gc.collect() 72 | 73 | if gc.garbage: 74 | say("Leftover garbage:" + str(gc.garbage)) 75 | else: 76 | say("Python's GC reports no leftover garbage") 77 | 78 | say("Memory usage after, RSS = %d, VSZ = %d" % get_memory_usage()) 79 | 80 | 81 | def get_memory_usage(): 82 | # `ps` has lots of format options that vary from OS to OS, and some of 83 | # those options have aliases (e.g. vsz, vsize). The ones I use below 84 | # appear to be the most portable. 85 | s = subprocess.Popen(["ps", "-p", str(os.getpid()), "-o", "rss,vsz"], 86 | stdout=subprocess.PIPE).communicate()[0] 87 | 88 | # Output looks like this: 89 | # RSZ VSZ 90 | # 944 75964 91 | 92 | s = s.decode(sys.getfilesystemencoding()) 93 | 94 | m = ps_output_regex.match(s) 95 | 96 | rsz = int(m.groups()[0]) 97 | vsz = int(m.groups()[1]) 98 | 99 | return rsz, vsz 100 | 101 | # chunks = [ item for item in s.split(' ') if item.strip() ] 102 | # 103 | # rss = chunks[0] 104 | # vsize = chunks[1] 105 | # 106 | # rss = int(rss.strip()) 107 | # vsize = int(vsize.strip()) 108 | # 109 | # return rss, vsize 110 | 111 | 112 | # Assert manual control over the garbage collector 113 | gc.disable() 114 | 115 | the_range = range(TEST_COUNT) 116 | 117 | if SKIP_SEMAPHORE_TESTS: 118 | say("Skipping semaphore tests") 119 | else: 120 | say("Running semaphore create/destroy test...") 121 | print_mem_before() 122 | 123 | for i in the_range: 124 | name = "/" + ''.join(random.sample(NAME_CHARACTERS, NAME_LENGTH)) 125 | sem = posix_ipc.Semaphore(name, posix_ipc.O_CREX) 126 | sem.unlink() 127 | sem.close() 128 | 129 | print_mem_after() 130 | 131 | say("Running semaphore create/destroy test 2...") 132 | print_mem_before() 133 | 134 | for i in the_range: 135 | name = "/" + ''.join(random.sample(NAME_CHARACTERS, NAME_LENGTH)) 136 | sem = posix_ipc.Semaphore(name, posix_ipc.O_CREX) 137 | sem.close() 138 | posix_ipc.unlink_semaphore(name) 139 | 140 | print_mem_after() 141 | 142 | say("Running semaphore create/destroy test 3...") 143 | print_mem_before() 144 | 145 | for i in the_range: 146 | sem = posix_ipc.Semaphore(None, posix_ipc.O_CREX) 147 | sem.unlink() 148 | sem.close() 149 | 150 | print_mem_after() 151 | 152 | say("Running semaphore create/destroy test 4...") 153 | print_mem_before() 154 | 155 | name = "/abcdefghijklm" 156 | sem = posix_ipc.Semaphore(name, posix_ipc.O_CREX) 157 | for i in the_range: 158 | try: 159 | foo = posix_ipc.Semaphore(name, posix_ipc.O_CREX) 160 | except posix_ipc.ExistentialError: 161 | pass 162 | 163 | sem.close() 164 | posix_ipc.unlink_semaphore(name) 165 | 166 | print_mem_after() 167 | 168 | say("Running semaphore acquire/release test...") 169 | print_mem_before() 170 | 171 | sem = posix_ipc.Semaphore("/p_ipc_test", posix_ipc.O_CREX) 172 | 173 | for i in the_range: 174 | sem.release() 175 | sem.acquire() 176 | 177 | sem.unlink() 178 | sem.close() 179 | 180 | print_mem_after() 181 | 182 | if posix_ipc.SEMAPHORE_TIMEOUT_SUPPORTED: 183 | say("Running semaphore acquire timeout test...") 184 | print_mem_before() 185 | 186 | sem = posix_ipc.Semaphore("/p_ipc_test", posix_ipc.O_CREX) 187 | 188 | for i in the_range: 189 | try: 190 | sem.acquire(.001) 191 | except posix_ipc.BusyError: 192 | pass 193 | 194 | sem.unlink() 195 | sem.close() 196 | 197 | print_mem_after() 198 | else: 199 | say("Skipping semaphore acquire timeout test (not supported on this platform)") 200 | 201 | say("Running semaphore name read test...") 202 | print_mem_before() 203 | 204 | sem = posix_ipc.Semaphore("/p_ipc_test", posix_ipc.O_CREX) 205 | 206 | for i in the_range: 207 | foo = sem.name 208 | 209 | sem.unlink() 210 | sem.close() 211 | 212 | print_mem_after() 213 | 214 | if posix_ipc.SEMAPHORE_VALUE_SUPPORTED: 215 | say("Running semaphore value read test...") 216 | print_mem_before() 217 | 218 | sem = posix_ipc.Semaphore("/p_ipc_test", posix_ipc.O_CREX) 219 | 220 | for i in the_range: 221 | foo = sem.value 222 | 223 | sem.unlink() 224 | sem.close() 225 | 226 | print_mem_after() 227 | else: 228 | say("Skipping semaphore value read test (not supported on this platform)") 229 | 230 | 231 | # ============== Memory tests ============== 232 | 233 | if SKIP_SHARED_MEMORY_TESTS: 234 | say("Skipping shared memory tests") 235 | else: 236 | say("Running memory create/destroy test...") 237 | print_mem_before() 238 | 239 | for i in the_range: 240 | name = "/" + ''.join(random.sample(NAME_CHARACTERS, NAME_LENGTH)) 241 | mem = posix_ipc.SharedMemory(name, posix_ipc.O_CREX, size=4096) 242 | 243 | os.close(mem.fd) 244 | 245 | mem.unlink() 246 | 247 | print_mem_after() 248 | 249 | say("Running memory create/destroy test 2...") 250 | print_mem_before() 251 | 252 | for i in the_range: 253 | name = "/" + ''.join(random.sample(NAME_CHARACTERS, NAME_LENGTH)) 254 | mem = posix_ipc.SharedMemory(name, posix_ipc.O_CREX, size=4096) 255 | 256 | os.close(mem.fd) 257 | 258 | posix_ipc.unlink_shared_memory(name) 259 | 260 | print_mem_after() 261 | 262 | say("Running memory create/destroy test 3...") 263 | print_mem_before() 264 | 265 | for i in the_range: 266 | mem = posix_ipc.SharedMemory(None, posix_ipc.O_CREX, size=4096) 267 | 268 | os.close(mem.fd) 269 | 270 | mem.unlink() 271 | 272 | print_mem_after() 273 | 274 | say("Running memory name read test...") 275 | print_mem_before() 276 | 277 | mem = posix_ipc.SharedMemory("/p_ipc_test", posix_ipc.O_CREX) 278 | 279 | for i in the_range: 280 | foo = mem.name 281 | 282 | mem.unlink() 283 | 284 | print_mem_after() 285 | 286 | say("Running memory fd read test...") 287 | print_mem_before() 288 | 289 | mem = posix_ipc.SharedMemory("/p_ipc_test", posix_ipc.O_CREX) 290 | 291 | for i in the_range: 292 | foo = mem.fd 293 | 294 | mem.unlink() 295 | 296 | print_mem_after() 297 | 298 | say("Running memory size read test...") 299 | print_mem_before() 300 | 301 | mem = posix_ipc.SharedMemory("/p_ipc_test", posix_ipc.O_CREX) 302 | 303 | for i in the_range: 304 | foo = mem.size 305 | 306 | mem.unlink() 307 | 308 | print_mem_after() 309 | 310 | say("Running memory size read test 2...") 311 | print_mem_before() 312 | 313 | mem = posix_ipc.SharedMemory("/p_ipc_test", posix_ipc.O_CREX, size=4096) 314 | 315 | for i in the_range: 316 | foo = mem.size 317 | 318 | mem.unlink() 319 | 320 | print_mem_after() 321 | 322 | # ============== Message queue tests ============== 323 | 324 | if SKIP_MESSAGE_QUEUE_TESTS: 325 | say("Skipping message queue tests") 326 | else: 327 | say("Running message queue create/destroy test...") 328 | print_mem_before() 329 | 330 | for i in the_range: 331 | name = "/" + ''.join(random.sample(NAME_CHARACTERS, NAME_LENGTH)) 332 | mq = posix_ipc.MessageQueue(name, posix_ipc.O_CREX) 333 | 334 | mq.close() 335 | mq.unlink() 336 | 337 | print_mem_after() 338 | 339 | say("Running message queue create/destroy test 2...") 340 | print_mem_before() 341 | 342 | for i in the_range: 343 | name = "/" + ''.join(random.sample(NAME_CHARACTERS, NAME_LENGTH)) 344 | mq = posix_ipc.MessageQueue(name, posix_ipc.O_CREX) 345 | 346 | mq.close() 347 | posix_ipc.unlink_message_queue(name) 348 | 349 | print_mem_after() 350 | 351 | say("Running message queue create/destroy test 3...") 352 | print_mem_before() 353 | 354 | for i in the_range: 355 | mq = posix_ipc.MessageQueue(None, posix_ipc.O_CREX) 356 | mq.close() 357 | mq.unlink() 358 | 359 | print_mem_after() 360 | 361 | say("Running message queue create/destroy test 4...") 362 | print_mem_before() 363 | 364 | name = "/abcdefghijklm" 365 | mq = posix_ipc.MessageQueue(name, posix_ipc.O_CREX) 366 | for i in the_range: 367 | try: 368 | foo = posix_ipc.MessageQueue(name, posix_ipc.O_CREX) 369 | except posix_ipc.ExistentialError: 370 | pass 371 | 372 | mq.close() 373 | posix_ipc.unlink_message_queue(name) 374 | 375 | print_mem_after() 376 | 377 | say("Running message queue send/receive() test with strings...") 378 | print_mem_before() 379 | 380 | mq = posix_ipc.MessageQueue("/p_ipc_test", posix_ipc.O_CREX) 381 | 382 | for i in the_range: 383 | mq.send(random_string(15)) 384 | mq.receive() 385 | 386 | mq.close() 387 | mq.unlink() 388 | 389 | print_mem_after() 390 | 391 | say("Running message queue send/receive() test with bytes...") 392 | print_mem_before() 393 | 394 | mq = posix_ipc.MessageQueue("/p_ipc_test", posix_ipc.O_CREX) 395 | 396 | for i in the_range: 397 | mq.send(random_string(15).encode("utf-8")) 398 | mq.receive() 399 | 400 | mq.close() 401 | mq.unlink() 402 | 403 | print_mem_after() 404 | 405 | say("Running lame message queue request_notification() test...") 406 | print_mem_before() 407 | 408 | mq = posix_ipc.MessageQueue("/p_ipc_test", posix_ipc.O_CREX) 409 | 410 | for i in the_range: 411 | mq.request_notification(posix_ipc.USER_SIGNAL_MIN) 412 | mq.request_notification(None) 413 | 414 | mq.close() 415 | mq.unlink() 416 | 417 | print_mem_after() 418 | 419 | say("Running message queue name read test...") 420 | print_mem_before() 421 | 422 | mq = posix_ipc.MessageQueue("/p_ipc_test", posix_ipc.O_CREX) 423 | 424 | for i in the_range: 425 | foo = mq.name 426 | 427 | mq.close() 428 | mq.unlink() 429 | 430 | print_mem_after() 431 | 432 | say("Running message queue mqd read test...") 433 | print_mem_before() 434 | 435 | mq = posix_ipc.MessageQueue("/p_ipc_test", posix_ipc.O_CREX) 436 | 437 | for i in the_range: 438 | foo = mq.mqd 439 | 440 | mq.close() 441 | mq.unlink() 442 | 443 | print_mem_after() 444 | 445 | say("Running message queue block read test...") 446 | print_mem_before() 447 | 448 | mq = posix_ipc.MessageQueue("/p_ipc_test", posix_ipc.O_CREX) 449 | 450 | for i in the_range: 451 | foo = mq.block 452 | 453 | mq.close() 454 | mq.unlink() 455 | 456 | print_mem_after() 457 | 458 | say("Running message queue block write test...") 459 | print_mem_before() 460 | 461 | mq = posix_ipc.MessageQueue("/p_ipc_test", posix_ipc.O_CREX) 462 | 463 | foo = mq.block 464 | for i in the_range: 465 | mq.block = foo 466 | 467 | mq.close() 468 | mq.unlink() 469 | 470 | print_mem_after() 471 | 472 | say("Running message queue max_messages read test...") 473 | print_mem_before() 474 | 475 | mq = posix_ipc.MessageQueue("/p_ipc_test", posix_ipc.O_CREX) 476 | 477 | for i in the_range: 478 | foo = mq.max_messages 479 | 480 | mq.close() 481 | mq.unlink() 482 | 483 | print_mem_after() 484 | 485 | say("Running message queue max_message_size read test...") 486 | print_mem_before() 487 | 488 | mq = posix_ipc.MessageQueue("/p_ipc_test", posix_ipc.O_CREX) 489 | 490 | for i in the_range: 491 | foo = mq.max_message_size 492 | 493 | mq.close() 494 | mq.unlink() 495 | 496 | print_mem_after() 497 | 498 | say("Running message queue current_messages read test...") 499 | print_mem_before() 500 | 501 | mq = posix_ipc.MessageQueue("/p_ipc_test", posix_ipc.O_CREX) 502 | 503 | for i in the_range: 504 | foo = mq.current_messages 505 | 506 | mq.close() 507 | mq.unlink() 508 | 509 | print_mem_after() 510 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = ["setuptools"] 3 | build-backend = "setuptools.build_meta" 4 | 5 | [project] 6 | name = "posix_ipc" 7 | dynamic = ["version", "license"] 8 | authors = [{name = "Philip Semanchuk", email = "philip@semanchuk.com"}] 9 | description = "POSIX IPC primitives (semaphores, shared memory and message queues) for Python" 10 | readme = "README.md" 11 | classifiers = [ 12 | "Development Status :: 5 - Production/Stable", 13 | "Intended Audience :: Developers", 14 | "Operating System :: MacOS :: MacOS X", 15 | "Operating System :: POSIX :: BSD :: FreeBSD", 16 | "Operating System :: POSIX :: Linux", 17 | "Operating System :: POSIX :: SunOS/Solaris", 18 | "Operating System :: POSIX", 19 | "Operating System :: Unix", 20 | "Programming Language :: Python", 21 | "Programming Language :: Python :: 3", 22 | "Topic :: Utilities", 23 | ] 24 | 25 | [project.urls] 26 | Homepage = "https://github.com/osvenskan/posix_ipc/" 27 | 28 | [tool.setuptools] 29 | # I set include-package-data to false, because when it's true (the default), 30 | # files might get included in the wheel that aren't needed. 31 | include-package-data = false 32 | 33 | [tool.setuptools.package-dir] 34 | posix_ipc = "src" 35 | 36 | [tool.setuptools.dynamic] 37 | version = {file = ["VERSION"]} 38 | -------------------------------------------------------------------------------- /sem_getvalue_test.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | /* This is a program for testing whether or not OSX supports sem_getvalue() 8 | yet. Use this to compile -- 9 | cc -Wall -o foo sem_getvalue_test.c -lpthread 10 | 11 | If sem_getvalue() is not supported, you'll get something like this -- 12 | sem_getvalue() returned -1, the semaphore's value is -99 13 | 14 | If sem_getvalue() is supported (e.g. on Linux), you'll get this -- 15 | sem_getvalue() returned 0, the semaphore's value is 1 16 | 17 | */ 18 | 19 | int main(void) { 20 | int rc; 21 | int sem_value = -99; // Init to nonsense value 22 | 23 | sem_t *pSemaphore = NULL; 24 | 25 | pSemaphore = sem_open("/my_semaphore", O_CREAT, 0600, 0); 26 | 27 | if (pSemaphore == SEM_FAILED) 28 | printf("Creating the semaphore failed; errno is %d\n", errno); 29 | 30 | 31 | rc = sem_post(pSemaphore); 32 | if (rc) 33 | printf("Releasing the semaphore failed; errno is %d\n", errno); 34 | 35 | rc = sem_getvalue(pSemaphore, &sem_value); 36 | printf("sem_getvalue() returned %d, the semaphore's value is %d\n", 37 | rc, sem_value); 38 | 39 | 40 | 41 | rc = sem_wait(pSemaphore); 42 | if (rc) 43 | printf("Acquiring the semaphore failed; errno is %d\n", errno); 44 | 45 | 46 | rc = sem_close(pSemaphore); 47 | 48 | return 0; 49 | } 50 | 51 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | import setuptools 2 | from setuptools.extension import Extension 3 | import sys 4 | 5 | # When python -m build runs, sys.path contains a minimum of entries. I add the current directory 6 | # to it (which is guaranteed by setuptools to be the project's root) so that I can import my 7 | # build_support tools. 8 | sys.path.append('.') 9 | import build_support.discover_system_info 10 | 11 | # As of April 2025, specifying the license metadata here (rather than in pyproject.toml) seems 12 | # like the best solution for now. See https://github.com/osvenskan/posix_ipc/issues/68 13 | LICENSE = "BSD-3-Clause" 14 | 15 | # As of April 2025, use of tool.setuptools.ext-modules is stil experimental in pyproject.toml. 16 | # Also, this code needs to dynamically adjust the `libraries` value that's passed to setuptools, 17 | # so I can't get rid of setup.py just yet. 18 | SOURCE_FILES = ["src/posix_ipc_module.c"] 19 | DEPENDS = ["src/posix_ipc_module.c", "src/system_info.h"] 20 | 21 | libraries = [] 22 | 23 | system_info = build_support.discover_system_info.discover() 24 | 25 | # Linux & FreeBSD require linking against the realtime libs. 26 | # This causes an error on other platforms 27 | if "REALTIME_LIB_IS_NEEDED" in system_info: 28 | libraries.append("rt") 29 | 30 | ext_modules = [Extension("posix_ipc", 31 | SOURCE_FILES, 32 | libraries=libraries, 33 | depends=DEPENDS, 34 | # -E is useful for debugging compile errors. 35 | # extra_compile_args=['-E'], 36 | )] 37 | 38 | setuptools.setup(ext_modules=ext_modules, 39 | license=LICENSE, 40 | ) 41 | -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/osvenskan/posix_ipc/e0ee0128534cfd774ff3091ad7f068264661d896/tests/__init__.py -------------------------------------------------------------------------------- /tests/base.py: -------------------------------------------------------------------------------- 1 | # Python imports 2 | import unittest 3 | import random 4 | import platform 5 | 6 | 7 | def _force_int(a_string): 8 | """Return the string as an int. If it can't be made into an int, return 0.""" 9 | try: 10 | an_int = int(a_string) 11 | except (ValueError, TypeError): 12 | an_int = 0 13 | 14 | return an_int 15 | 16 | 17 | # Lots of code here to determine if the FreeBSD version is <= 10.2. Those versions contain a 18 | # bug that causes a hang or seg fault if I exercise certain portions of the semaphore tests. 19 | IS_FREEBSD = (platform.system().lower() == 'freebsd') 20 | FREEBSD_VERSION_MINOR = 0 21 | FREEBSD_VERSION_MAJOR = 0 22 | 23 | if IS_FREEBSD: 24 | # I want to get the release number. Here's some samples of what I've seen in platform.release(): 25 | # PC BSD 10.2: '10.2-RELEASE-p14' 26 | # FreeBSD 9.1: '9.1-RELEASE-p7' 27 | # I want the number at the beginning. The code below attempts to extract it, but if it runs 28 | # into anything unexpected it stops trying rather than raising an error. 29 | release = platform.release().split('-')[0] 30 | if '.' in release: 31 | major, minor = release.split('.', 2) 32 | FREEBSD_VERSION_MAJOR = _force_int(major) 33 | FREEBSD_VERSION_MINOR = _force_int(minor) 34 | # else: 35 | # This isn't in the format I expect, so I don't try to parse it. 36 | 37 | # https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=206396 38 | HAS_FREEBSD_BUG_206396 = IS_FREEBSD and (FREEBSD_VERSION_MAJOR <= 10) and \ 39 | (FREEBSD_VERSION_MINOR <= 2) 40 | FREEBSD_BUG_206396_SKIP_MSG = \ 41 | 'Feature buggy on this platform; see https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=206396' 42 | 43 | 44 | def make_name(): 45 | """Generate a random name suitable for an IPC object.""" 46 | alphabet = 'abcdefghijklmnopqrstuvwxyz' 47 | return '/' + ''.join(random.sample(alphabet, random.randint(3, 12))) 48 | 49 | 50 | class Base(unittest.TestCase): 51 | """Base class for test cases.""" 52 | def assertWriteToReadOnlyPropertyFails(self, target_object, property_name, 53 | value): 54 | """test that writing to a readonly property raises an exception""" 55 | # The attributes tested with this code are implemented differently in C. 56 | # For instance, Semaphore.value is a 'getseters' with a NULL setter, 57 | # whereas Semaphore.name is a reference into the Semaphore member 58 | # definition. 59 | # Under Python 2.6, writing to sem.value raises AttributeError whereas 60 | # writing to sem.name raises TypeError. Under Python 3, both raise 61 | # AttributeError (but with different error messages!). 62 | # This illustrates that Python is a little unpredictable in this 63 | # matter. Rather than testing each of the numerous combinations of 64 | # of Python versions and attribute implementation, I just accept 65 | # both TypeError and AttributeError here. 66 | # ref: http://bugs.python.org/issue1687163 67 | # ref: http://bugs.python.org/msg127173 68 | with self.assertRaises((TypeError, AttributeError)): 69 | setattr(target_object, property_name, value) 70 | -------------------------------------------------------------------------------- /tests/test_memory.py: -------------------------------------------------------------------------------- 1 | # Python imports 2 | import numbers 3 | import platform 4 | import unittest 5 | import mmap 6 | import os 7 | import sys 8 | 9 | # Project imports 10 | import posix_ipc 11 | 12 | from . import base as tests_base 13 | 14 | _IS_MACOS = "Darwin" in platform.uname() 15 | 16 | 17 | class TestMemory(tests_base.Base): 18 | """Exercise the SharedMemory class""" 19 | # SIZE should be something that's not a power of 2 since that's more 20 | # likely to expose odd behavior. 21 | SIZE = 3333 22 | 23 | def setUp(self): 24 | self.mem = posix_ipc.SharedMemory('/foo', posix_ipc.O_CREX, 25 | size=self.SIZE) 26 | 27 | def tearDown(self): 28 | if self.mem: 29 | self.mem.close_fd() 30 | self.mem.unlink() 31 | 32 | def assertWriteToReadOnlyPropertyFails(self, property_name, value): 33 | """test that writing to a readonly property raises TypeError""" 34 | tests_base.Base.assertWriteToReadOnlyPropertyFails(self, self.mem, 35 | property_name, value) 36 | 37 | def test_ctor_no_flags_existing(self): 38 | """tests that opening a memory segment with no flags opens the existing 39 | memory and doesn't create a new segment""" 40 | mem_copy = posix_ipc.SharedMemory('/foo') 41 | self.assertEqual(self.mem.name, mem_copy.name) 42 | 43 | def test_ctor_no_flags_non_existent(self): 44 | """test that attempting to open a non-existent memory segment with no 45 | flags fails""" 46 | self.assertRaises(posix_ipc.ExistentialError, posix_ipc.SharedMemory, 47 | '/fjksfjkhsdakh') 48 | 49 | def test_ctor_o_creat_existing(self): 50 | """tests posix_ipc.O_CREAT to open an existing segment without O_EXCL""" 51 | mem_copy = posix_ipc.SharedMemory(self.mem.name, posix_ipc.O_CREAT) 52 | 53 | self.assertEqual(self.mem.name, mem_copy.name) 54 | 55 | def test_o_creat_new(self): 56 | """tests posix_ipc.O_CREAT to create a new mem segment without O_EXCL""" 57 | mem = posix_ipc.SharedMemory('/lsdhfkjahdskjf', posix_ipc.O_CREAT, 58 | size=4096) 59 | self.assertIsNotNone(mem) 60 | mem.close_fd() 61 | mem.unlink() 62 | 63 | def test_o_excl(self): 64 | """tests O_CREAT | O_EXCL prevents opening an existing memory segment""" 65 | self.assertRaises(posix_ipc.ExistentialError, posix_ipc.SharedMemory, 66 | '/foo', posix_ipc.O_CREAT | posix_ipc.O_EXCL) 67 | 68 | def test_o_crex(self): 69 | """tests O_CREX prevents opening an existing memory segment""" 70 | self.assertRaises(posix_ipc.ExistentialError, posix_ipc.SharedMemory, 71 | '/foo', posix_ipc.O_CREX) 72 | 73 | @unittest.skipIf(_IS_MACOS, "O_TRUNC is not supported under macOS") 74 | def test_o_trunc(self): 75 | """Test that O_TRUNC truncates the memory to 0 bytes""" 76 | mem_copy = posix_ipc.SharedMemory(self.mem.name, posix_ipc.O_TRUNC) 77 | 78 | self.assertEqual(mem_copy.size, 0) 79 | 80 | def test_randomly_generated_name(self): 81 | """tests that the randomly-generated name works""" 82 | mem = posix_ipc.SharedMemory(None, posix_ipc.O_CREX, size=1024) 83 | self.assertIsNotNone(mem.name) 84 | self.assertEqual(mem.name[0], '/') 85 | self.assertGreaterEqual(len(mem.name), 2) 86 | mem.close_fd() 87 | mem.unlink() 88 | 89 | def test_name_as_bytes(self): 90 | """Test that the name can be bytes.""" 91 | name = bytes(tests_base.make_name(), 'ASCII') 92 | mem = posix_ipc.SharedMemory(name, posix_ipc.O_CREX, size=4096) 93 | self.assertEqual(name, bytes(mem.name, 'ASCII')) 94 | mem.close_fd() 95 | mem.unlink() 96 | 97 | # # don't bother testing mode, it's ignored by the OS? 98 | 99 | def test_mmap_size(self): 100 | """test that the size specified is (somewhat) respected by mmap()""" 101 | # In limited testing, Linux respects the exact size specified in the 102 | # SharedMemory() ctor when creating the mmapped file. 103 | # e.g. when self.SIZE = 3333, the 104 | # mmapped file is also 3333 bytes. 105 | # 106 | # macOS's mmapped files always have sizes that are mod mmap.PAGESIZE. 107 | # 108 | # I haven't tested other operating systems. 109 | # 110 | # AFAICT the specification doesn't demand that the size has to match 111 | # exactly, so this code accepts either value as correct. 112 | 113 | delta = self.SIZE % mmap.PAGESIZE 114 | 115 | if delta: 116 | # Round up to nearest block size 117 | crude_size = (self.SIZE - delta) + mmap.PAGESIZE 118 | else: 119 | crude_size = self.SIZE 120 | 121 | self.assertIn(self.mem.size, (self.SIZE, crude_size)) 122 | 123 | f = mmap.mmap(self.mem.fd, self.SIZE) 124 | 125 | self.assertIn(f.size(), (self.SIZE, crude_size)) 126 | 127 | f.close() 128 | 129 | def test_ctor_read_only_flag(self): 130 | """test that specifying the readonly flag prevents writing""" 131 | mem = posix_ipc.SharedMemory(self.mem.name, read_only=True) 132 | f = mmap.mmap(mem.fd, self.mem.size, prot=mmap.PROT_READ) 133 | self.assertRaises(TypeError, f.write, 'hello world') 134 | mem.close_fd() 135 | 136 | @unittest.skipUnless(sys.stdin.fileno() == 0, "Requires stdin to have file number 0") 137 | def test_ctor_fd_can_become_zero(self): 138 | """test that SharedMemory accepts 0 as valid file descriptor""" 139 | # ref: https://github.com/osvenskan/posix_ipc/issues/2 140 | # This test relies on OS compliance with the POSIX spec. Specifically, the spec for 141 | # shm_open() says -- 142 | # 143 | # shm_open() shall return a file descriptor for the shared memory 144 | # object that is the lowest numbered file descriptor not currently 145 | # open for that process. 146 | # 147 | # ref: http://pubs.opengroup.org/onlinepubs/009695399/functions/shm_open.html 148 | # 149 | # So, on systems compliant with that particular part of the spec, if I open a SharedMemory 150 | # segment after closing stdin (which has fd == 0), the SharedMemory segment, should be 151 | # assigned fd 0. 152 | os.close(0) 153 | 154 | # I have to supply a size here, otherwise the call to close_fd() will fail under macOS. 155 | # See here for another report of the same behavior: 156 | # https://stackoverflow.com/questions/35371133/close-on-shared-memory-in-osx-causes-invalid-argument-error 157 | mem = posix_ipc.SharedMemory(None, posix_ipc.O_CREX, size=4096) 158 | mem_fd = mem.fd 159 | # Clean up before attempting the assertion in case the assertion fails. 160 | mem.close_fd() 161 | mem.unlink() 162 | 163 | self.assertEqual(mem_fd, 0) 164 | 165 | def test_object_method_close_fd(self): 166 | """test that SharedMemory.close_fd() closes the file descriptor""" 167 | mem = posix_ipc.SharedMemory(self.mem.name) 168 | 169 | mem.close_fd() 170 | 171 | self.assertEqual(mem.fd, -1) 172 | 173 | # On at least one platform (my Mac), this raises OSError under Python 2.7 and ValueError 174 | # under Python 3.6. 175 | with self.assertRaises((OSError, ValueError)): 176 | os.fdopen(mem.fd) 177 | 178 | def test_unlink(self): 179 | """test that SharedMemory.unlink() deletes the segment""" 180 | name = self.mem.name 181 | self.mem.close_fd() 182 | self.mem.unlink() 183 | self.assertRaises(posix_ipc.ExistentialError, getattr, 184 | self.mem, 'size') 185 | self.assertRaises(posix_ipc.ExistentialError, posix_ipc.SharedMemory, 186 | name) 187 | self.mem = None 188 | 189 | def test_name_property(self): 190 | """exercise SharedMemory.name""" 191 | self.assertGreaterEqual(len(self.mem.name), 2) 192 | 193 | self.assertEqual(self.mem.name[0], '/') 194 | 195 | self.assertWriteToReadOnlyPropertyFails('name', 'hello world') 196 | 197 | def test_fd_property(self): 198 | """exercise SharedMemory.fd""" 199 | self.assertIsInstance(self.mem.fd, numbers.Integral) 200 | 201 | self.assertWriteToReadOnlyPropertyFails('fd', 42) 202 | 203 | def test_fileno(self): 204 | """exercise SharedMemory.fileno""" 205 | self.assertEqual(self.mem.fd, self.mem.fileno()) 206 | 207 | def test_size_property(self): 208 | """exercise SharedMemory.size""" 209 | self.assertIsInstance(self.mem.size, numbers.Integral) 210 | 211 | self.assertWriteToReadOnlyPropertyFails('size', 42) 212 | 213 | 214 | class TestMemoryResize(tests_base.Base): 215 | """Exercise various aspects of resizing an existing SharedMemory segment. 216 | 217 | The more interesting aspects of this test don't run under macOS because resizing isn't 218 | supported on that platform. 219 | """ 220 | def setUp(self): 221 | # In constrast to the other memory test that deliberately uses an odd size, this test 222 | # uses a product of the system's block size. As noted above, the spec doesn't require 223 | # the segment to exactly respect the specified size. In practice probably all systems 224 | # respect it as long as it's evenly divisible by the block size. 225 | # One of the tests in this case attempts to cut the memory size in half, and I want to 226 | # mitigate the possibility that it will fail due to a platform-specific implementation 227 | # (e.g. trying to create a segment that's smaller than some arbitrary OS minimum). 228 | # Creating a segment that's twice the size of the block size seems the best option since 229 | # dividing it in half still leaves a segment size that's likely to be acceptable on 230 | # all platforms. 231 | self.original_size = mmap.PAGESIZE * 2 232 | self.mem = posix_ipc.SharedMemory(None, posix_ipc.O_CREX, size=self.original_size) 233 | 234 | def tearDown(self): 235 | if self.mem: 236 | self.mem.close_fd() 237 | self.mem.unlink() 238 | 239 | def test_ctor_second_handle_default_size_no_change(self): 240 | """opening an existing segment with the default size shouldn't change the size.""" 241 | mem = posix_ipc.SharedMemory(self.mem.name) 242 | self.assertEqual(mem.size, self.original_size) 243 | self.assertEqual(self.mem.size, self.original_size) 244 | mem.close_fd() 245 | 246 | def test_ctor_second_handle_explicit_size_no_change(self): 247 | """opening an existing segment with an explicit size of 0 shouldn't change the size.""" 248 | mem = posix_ipc.SharedMemory(self.mem.name, size=0) 249 | self.assertEqual(mem.size, self.original_size) 250 | self.assertEqual(self.mem.size, self.original_size) 251 | mem.close_fd() 252 | 253 | @unittest.skipIf(_IS_MACOS, "Changing shared memory size is not supported under macOS") 254 | def test_ctor_second_handle_size_increase(self): 255 | """exercise increasing the size of an existing segment via a second handle to it""" 256 | new_size = self.original_size * 2 257 | mem = posix_ipc.SharedMemory(self.mem.name, size=new_size) 258 | self.assertEqual(mem.size, new_size) 259 | self.assertEqual(self.mem.size, new_size) 260 | mem.close_fd() 261 | 262 | @unittest.skipIf(_IS_MACOS, "Changing shared memory size is not supported under macOS") 263 | def test_ctor_second_handle_size_decrease(self): 264 | """exercise decreasing the size of an existing segment via a second handle to it""" 265 | new_size = self.original_size // 2 266 | mem = posix_ipc.SharedMemory(self.mem.name, size=new_size) 267 | self.assertEqual(mem.size, new_size) 268 | self.assertEqual(self.mem.size, new_size) 269 | mem.close_fd() 270 | 271 | def test_ftruncate_increase(self): 272 | """exercise increasing the size of an existing segment from 0 via ftruncate()""" 273 | mem = posix_ipc.SharedMemory(None, posix_ipc.O_CREX) 274 | self.assertEqual(mem.size, 0) 275 | new_size = mmap.PAGESIZE 276 | os.ftruncate(mem.fd, new_size) 277 | self.assertEqual(mem.size, new_size) 278 | 279 | 280 | if __name__ == '__main__': 281 | unittest.main() 282 | -------------------------------------------------------------------------------- /tests/test_message_queues.py: -------------------------------------------------------------------------------- 1 | # Python imports 2 | import unittest 3 | from unittest import skipUnless 4 | import time 5 | import signal 6 | import threading 7 | 8 | # Project imports 9 | import posix_ipc 10 | 11 | from . import base as tests_base 12 | 13 | if hasattr(posix_ipc, 'USER_SIGNAL_MIN'): 14 | # Due to Python bug http://bugs.python.org/issue20584, not all valid signal 15 | # values can be used. I noticed it on FreeBSD, it might be visible 16 | # elsewhere. 17 | if posix_ipc.USER_SIGNAL_MIN >= signal.NSIG: 18 | SIGNAL_VALUE = signal.SIGHUP 19 | else: 20 | SIGNAL_VALUE = posix_ipc.USER_SIGNAL_MIN 21 | else: 22 | SIGNAL_VALUE = signal.SIGHUP 23 | 24 | signal_handler_value_received = 0 25 | 26 | 27 | def signal_handler(signal_value, frame): 28 | """Handle signal sent for msg q notification test.""" 29 | global signal_handler_value_received 30 | signal_handler_value_received = signal_value 31 | 32 | 33 | def threaded_notification_handler_one_shot(test_case_instance): 34 | """Handle msg q notification in a thread without rearming notification.""" 35 | test_case_instance.threaded_notification_called = True 36 | test_case_instance.notification_event.set() 37 | 38 | 39 | def threaded_notification_handler_rearm(test_case_instance): 40 | """Handle msg q notification in a thread and rearm notification.""" 41 | # Rearm. 42 | param = (threaded_notification_handler_rearm, test_case_instance) 43 | test_case_instance.mq.request_notification(param) 44 | 45 | test_case_instance.threaded_notification_called = True 46 | test_case_instance.notification_event.set() 47 | 48 | 49 | class MessageQueueTestBase(tests_base.Base): 50 | """base class for MessageQueue test classes""" 51 | def setUp(self): 52 | # Create a small queue to reduce the likelihood of resource exhaustion 53 | # See https://github.com/osvenskan/posix_ipc/issues/42 54 | self.mq = posix_ipc.MessageQueue(None, posix_ipc.O_CREX, 55 | max_messages=10, 56 | max_message_size=10) 57 | 58 | def tearDown(self): 59 | if self.mq: 60 | self.mq.close() 61 | self.mq.unlink() 62 | 63 | def assertWriteToReadOnlyPropertyFails(self, property_name, value): 64 | """test that writing to a readonly property raises TypeError""" 65 | tests_base.Base.assertWriteToReadOnlyPropertyFails(self, self.mq, property_name, value) 66 | 67 | 68 | @skipUnless(posix_ipc.MESSAGE_QUEUES_SUPPORTED, "Requires MessageQueue support") 69 | class TestMessageQueueCreation(MessageQueueTestBase): 70 | """Exercise stuff related to creating MessageQueue""" 71 | def test_no_flags(self): 72 | """tests that opening a queue with no flags opens the existing 73 | queue and doesn't create a new queue""" 74 | mq_copy = posix_ipc.MessageQueue(self.mq.name) 75 | self.assertEqual(self.mq.name, mq_copy.name) 76 | mq_copy.close() 77 | 78 | def test_o_creat_existing(self): 79 | """tests posix_ipc.O_CREAT to open an existing MessageQueue without 80 | O_EXCL""" 81 | mq_copy = posix_ipc.MessageQueue(self.mq.name, posix_ipc.O_CREAT) 82 | self.assertEqual(self.mq.name, mq_copy.name) 83 | mq_copy.close() 84 | 85 | def test_o_creat_new(self): 86 | """tests posix_ipc.O_CREAT to create a new MessageQueue without 87 | O_EXCL""" 88 | # I can't pass None for the name unless I also pass O_EXCL. 89 | name = tests_base.make_name() 90 | 91 | # Note: this method of finding an unused name is vulnerable to a 92 | # race condition. It's good enough for test, but don't copy it for 93 | # use in production code! 94 | name_is_available = False 95 | while not name_is_available: 96 | try: 97 | mq = posix_ipc.MessageQueue(name) 98 | mq.close() 99 | except posix_ipc.ExistentialError: 100 | name_is_available = True 101 | else: 102 | name = tests_base.make_name() 103 | 104 | mq = posix_ipc.MessageQueue(name, posix_ipc.O_CREAT) 105 | self.assertIsNotNone(mq) 106 | mq.close() 107 | mq.unlink() 108 | 109 | def test_o_crex(self): 110 | """tests O_CREX prevents opening an existing MessageQueue""" 111 | self.assertRaises(posix_ipc.ExistentialError, posix_ipc.MessageQueue, 112 | self.mq.name, posix_ipc.O_CREX) 113 | 114 | def test_randomly_generated_name(self): 115 | """tests that the randomly-generated name works""" 116 | # This is tested implicitly elsewhere but I want an explicit test 117 | mq = posix_ipc.MessageQueue(None, posix_ipc.O_CREX) 118 | self.assertIsNotNone(mq.name) 119 | 120 | self.assertEqual(mq.name[0], '/') 121 | self.assertGreaterEqual(len(mq.name), 2) 122 | mq.close() 123 | mq.unlink() 124 | 125 | def test_name_as_bytes(self): 126 | """Test that the name can be bytes.""" 127 | name = bytes(tests_base.make_name(), 'ASCII') 128 | mq = posix_ipc.MessageQueue(name, posix_ipc.O_CREX) 129 | self.assertEqual(name, bytes(mq.name, 'ASCII')) 130 | mq.close() 131 | mq.unlink() 132 | 133 | # don't bother testing mode, it's ignored by the OS? 134 | 135 | def test_max_messages(self): 136 | """test that the max_messages param is respected""" 137 | mq = posix_ipc.MessageQueue(None, posix_ipc.O_CREX, max_messages=1) 138 | mq.send('foo') 139 | self.assertRaises(posix_ipc.BusyError, mq.send, 'bar', timeout=0) 140 | mq.close() 141 | mq.unlink() 142 | 143 | def test_max_message_size(self): 144 | """test that the max_message_size param is respected""" 145 | mq = posix_ipc.MessageQueue(None, posix_ipc.O_CREX, 146 | max_message_size=10) 147 | self.assertRaises(ValueError, mq.send, ' ' * 11) 148 | mq.close() 149 | mq.unlink() 150 | 151 | def test_read_flag_new_queue(self): 152 | """test that the read flag is respected on a new queue""" 153 | mq = posix_ipc.MessageQueue(None, posix_ipc.O_CREX, read=False) 154 | mq.send('foo') 155 | self.assertRaises(posix_ipc.PermissionsError, mq.receive) 156 | mq.close() 157 | mq.unlink() 158 | 159 | def test_read_flag_existing_queue(self): 160 | """test that the read flag is respected on an existing queue""" 161 | mq = posix_ipc.MessageQueue(self.mq.name, read=False) 162 | mq.send('foo') 163 | self.assertRaises(posix_ipc.PermissionsError, mq.receive) 164 | mq.close() 165 | 166 | def test_write_flag_new_queue(self): 167 | """test that the write flag is respected on a new queue""" 168 | mq = posix_ipc.MessageQueue(None, posix_ipc.O_CREX, write=False) 169 | self.assertRaises(posix_ipc.PermissionsError, mq.send, 'foo') 170 | mq.close() 171 | mq.unlink() 172 | 173 | def test_write_flag_existing_queue(self): 174 | """test that the write flag is respected on an existing queue""" 175 | mq = posix_ipc.MessageQueue(self.mq.name, write=False) 176 | self.assertRaises(posix_ipc.PermissionsError, mq.send, 'foo') 177 | mq.close() 178 | 179 | def test_kwargs(self): 180 | """ensure init accepts keyword args as advertised""" 181 | mq = posix_ipc.MessageQueue(None, flags=posix_ipc.O_CREX, mode=0o0600, max_messages=1, 182 | max_message_size=256, read=True, write=True) 183 | mq.close() 184 | mq.unlink() 185 | 186 | 187 | @skipUnless(posix_ipc.MESSAGE_QUEUES_SUPPORTED, "Requires MessageQueue support") 188 | class TestMessageQueueSendReceive(MessageQueueTestBase): 189 | """Exercise send() and receive()""" 190 | def test_send(self): 191 | """Test that simple send works. 192 | 193 | It's already tested elsewhere implicitly, but I want an explicit 194 | test. 195 | """ 196 | self.mq.send('foo') 197 | 198 | # FIXME how to test that send with timeout=None waits as expected? 199 | 200 | def test_send_timeout_keyword(self): 201 | """Test that the timeout keyword of send works""" 202 | n_msgs = self.mq.max_messages 203 | while n_msgs: 204 | self.mq.send(' ') 205 | n_msgs -= 1 206 | 207 | self.assertRaises(posix_ipc.BusyError, self.mq.send, 'foo', timeout=0) 208 | 209 | def test_send_timeout_positional(self): 210 | """Test that the timeout positional param of send works""" 211 | n_msgs = self.mq.max_messages 212 | while n_msgs: 213 | self.mq.send(' ') 214 | n_msgs -= 1 215 | 216 | self.assertRaises(posix_ipc.BusyError, self.mq.send, 'foo', 0) 217 | 218 | def test_send_timeout_zero_success(self): 219 | """Test that send w/a timeout=0 succeeds if queue is not full.""" 220 | self.mq.send('foo', timeout=0) 221 | 222 | def test_send_timeout_zero_fails(self): 223 | """Test that send w/a timeout=0 raises BusyError if queue is 224 | full. 225 | """ 226 | n_msgs = self.mq.max_messages 227 | while n_msgs: 228 | self.mq.send(' ') 229 | n_msgs -= 1 230 | 231 | self.assertRaises(posix_ipc.BusyError, self.mq.send, 'foo', 232 | timeout=0) 233 | 234 | def test_send_nonzero_timeout(self): 235 | """Test that a non-zero timeout to send is respected.""" 236 | n_msgs = self.mq.max_messages 237 | while n_msgs: 238 | self.mq.send(' ') 239 | n_msgs -= 1 240 | 241 | start = time.time() 242 | self.assertRaises(posix_ipc.BusyError, self.mq.send, ' ', 243 | timeout=1.0) 244 | elapsed = time.time() - start 245 | # I don't insist on extreme precision. 246 | self.assertTrue(elapsed >= 1.0) 247 | self.assertTrue(elapsed < 1.5) 248 | 249 | def test_send_priority_default(self): 250 | """Test that the send priority defaults to 0""" 251 | self.mq.send('foo') 252 | self.assertEqual(self.mq.receive(), ('foo'.encode(), 0)) 253 | 254 | def test_send_fifo_default(self): 255 | """Test that the queue order is FIFO by default""" 256 | alphabet = 'abcdefg' 257 | for c in alphabet: 258 | self.mq.send(c) 259 | 260 | for c in alphabet: 261 | self.assertEqual(self.mq.receive(), (c.encode(), 0)) 262 | 263 | def test_send_priority(self): 264 | """Test that the priority param is respected""" 265 | # By simple FIFO, these would be returned lowest, highest, middle. 266 | # Instead they'll be returned highest, middle, lowest 267 | self.mq.send('lowest', priority=1) 268 | self.mq.send('highest', priority=3) 269 | self.mq.send('middle', priority=2) 270 | 271 | self.assertEqual(self.mq.receive(), ('highest'.encode(), 3)) 272 | self.assertEqual(self.mq.receive(), ('middle'.encode(), 2)) 273 | self.assertEqual(self.mq.receive(), ('lowest'.encode(), 1)) 274 | 275 | def test_send_priority_keyword(self): 276 | """Test that the priority keyword of send works""" 277 | self.mq.send('foo', priority=42) 278 | self.assertEqual(self.mq.receive(), ('foo'.encode(), 42)) 279 | 280 | def test_send_priority_positional(self): 281 | """Test that the priority positional param of send works""" 282 | self.mq.send('foo', 0, 42) 283 | self.assertEqual(self.mq.receive(), ('foo'.encode(), 42)) 284 | 285 | def test_send_kwargs(self): 286 | """ensure send() accepts keyword args as advertised""" 287 | self.mq.send('foo', timeout=0, priority=0) 288 | 289 | # ##### test receive() 290 | 291 | def test_receive(self): 292 | """Test that simple receive works. 293 | 294 | It's already tested elsewhere implicitly, but I want an explicit 295 | test. 296 | """ 297 | self.mq.send('foo', priority=3) 298 | 299 | self.assertEqual(self.mq.receive(), ('foo'.encode(), 3)) 300 | 301 | def test_receive_timeout_positional(self): 302 | """Test that the timeout positional param of receive works.""" 303 | self.assertRaises(posix_ipc.BusyError, self.mq.receive, 0) 304 | 305 | def test_receive_nonzero_timeout(self): 306 | """Test that a non-zero timeout to receive is respected.""" 307 | start = time.time() 308 | self.assertRaises(posix_ipc.BusyError, self.mq.receive, 1.0) 309 | elapsed = time.time() - start 310 | # I don't insist on extreme precision. 311 | self.assertTrue(elapsed >= 1.0) 312 | self.assertTrue(elapsed < 1.5) 313 | 314 | # FIXME how to test that timeout=None waits forever? 315 | 316 | 317 | @skipUnless(posix_ipc.MESSAGE_QUEUES_SUPPORTED, "Requires MessageQueue support") 318 | class TestMessageQueueNotification(MessageQueueTestBase): 319 | """exercise request_notification()""" 320 | def test_request_notification_signal(self): 321 | """Exercise notification by signal""" 322 | global someone_rang_the_doorbell 323 | 324 | self.mq.request_notification(SIGNAL_VALUE) 325 | 326 | signal.signal(SIGNAL_VALUE, signal_handler) 327 | 328 | someone_rang_the_doorbell = False 329 | 330 | self.mq.send('') 331 | 332 | self.assertEqual(signal_handler_value_received, SIGNAL_VALUE) 333 | 334 | def test_request_notification_signal_one_shot(self): 335 | """Test that after notification by signal, notification is 336 | cancelled""" 337 | global signal_handler_value_received 338 | 339 | self.mq.request_notification(SIGNAL_VALUE) 340 | 341 | signal.signal(SIGNAL_VALUE, signal_handler) 342 | 343 | signal_handler_value_received = 0 344 | 345 | self.mq.send('') 346 | 347 | self.assertEqual(signal_handler_value_received, SIGNAL_VALUE) 348 | 349 | # Reset the global flag 350 | signal_handler_value_received = 0 351 | 352 | self.mq.send('') 353 | 354 | # Flag should not be set because it's only supposed to fire 355 | # notification when the queue changes from empty to non-empty, 356 | # and there was already 1 msg in the Q when I called send() above. 357 | self.assertEqual(signal_handler_value_received, 0) 358 | 359 | # empty the queue 360 | self.mq.receive() 361 | self.mq.receive() 362 | 363 | self.assertEqual(signal_handler_value_received, 0) 364 | 365 | self.mq.send('') 366 | 367 | # Flag should still not be set because notification was cancelled 368 | # after it fired the first time. 369 | self.assertEqual(signal_handler_value_received, 0) 370 | 371 | def test_request_notification_cancel_default(self): 372 | """Test that notification can be cancelled with the default param""" 373 | global signal_handler_value_received 374 | 375 | self.mq.request_notification(SIGNAL_VALUE) 376 | 377 | signal.signal(SIGNAL_VALUE, signal_handler) 378 | 379 | signal_handler_value_received = 0 380 | 381 | # Cancel notification 382 | self.mq.request_notification() 383 | 384 | self.mq.send('') 385 | 386 | self.assertEqual(signal_handler_value_received, 0) 387 | 388 | def test_request_notification_cancel_multiple(self): 389 | """Test that notification can be cancelled multiple times""" 390 | self.mq.request_notification(SIGNAL_VALUE) 391 | 392 | # Cancel notification lots of times 393 | for i in range(1000): 394 | self.mq.request_notification() 395 | 396 | def test_request_notification_threaded_one_shot(self): 397 | """Test simple threaded notification""" 398 | 399 | self.threaded_notification_called = False 400 | 401 | param = (threaded_notification_handler_one_shot, self) 402 | self.mq.request_notification(param) 403 | 404 | self.notification_event = threading.Event() 405 | 406 | self.mq.send('') 407 | 408 | # I have to wait on a shared event to ensure that the notification 409 | # handler's thread gets a chance to execute before I make my 410 | # assertion. 411 | self.notification_event.wait(5) 412 | 413 | self.assertTrue(self.threaded_notification_called) 414 | 415 | def test_request_notification_threaded_rearm(self): 416 | """Test threaded notification in which the notified thread rearms 417 | the notification""" 418 | 419 | self.threaded_notification_called = False 420 | 421 | param = (threaded_notification_handler_rearm, self) 422 | self.mq.request_notification(param) 423 | 424 | self.notification_event = threading.Event() 425 | 426 | # Re-arm several times. 427 | for i in range(10): 428 | self.mq.send('') 429 | 430 | # I have to wait on a shared event to ensure that the 431 | # notification handler's thread gets a chance to execute before 432 | # I make my assertion. 433 | self.notification_event.wait(5) 434 | 435 | self.assertTrue(self.threaded_notification_called) 436 | 437 | self.mq.receive() 438 | 439 | self.notification_event.clear() 440 | 441 | 442 | @skipUnless(posix_ipc.MESSAGE_QUEUES_SUPPORTED, "Requires MessageQueue support") 443 | class TestMessageQueueDestruction(MessageQueueTestBase): 444 | """exercise close() and unlink()""" 445 | 446 | def test_close_and_unlink(self): 447 | """tests that mq.close() and mq.unlink() work""" 448 | # mq.close() is hard to test since subsequent use of the queue 449 | # after mq.close() is undefined. All I can think of to do is call it 450 | # and note that it does not fail. Also, it allows mq.unlink() to 451 | # tell the OS to delete the queue entirely, so it makes sense 452 | # to test them together, 453 | self.mq.unlink() 454 | self.mq.close() 455 | self.assertRaises(posix_ipc.ExistentialError, posix_ipc.MessageQueue, 456 | self.mq.name) 457 | 458 | # Wipe this out so that self.tearDown() doesn't crash. 459 | self.mq = None 460 | 461 | 462 | @skipUnless(posix_ipc.MESSAGE_QUEUES_SUPPORTED, "Requires MessageQueue support") 463 | class TestMessageQueuePropertiesAndAttributes(MessageQueueTestBase): 464 | """Exercise props and attrs""" 465 | def test_property_name(self): 466 | """exercise MessageQueue.name""" 467 | self.assertGreaterEqual(len(self.mq.name), 2) 468 | 469 | self.assertEqual(self.mq.name[0], '/') 470 | 471 | self.assertWriteToReadOnlyPropertyFails('name', 'hello world') 472 | 473 | def test_property_mqd(self): 474 | """exercise MessageQueue.mqd""" 475 | self.assertNotEqual(self.mq.mqd, -1) 476 | 477 | # The mqd is of type mqd_t. I can't find doc that states what this 478 | # type is. All I know is that -1 is an error so it's probably 479 | # int-ish, but I can't tell exactly what to expect. 480 | self.assertWriteToReadOnlyPropertyFails('mqd', 42) 481 | 482 | def test_fileno(self): 483 | """exercise MessageQueue.fileno()""" 484 | self.assertEqual(self.mq.mqd, self.mq.fileno()) 485 | 486 | def test_property_max_messages(self): 487 | """exercise MessageQueue.max_messages""" 488 | self.assertGreaterEqual(self.mq.max_messages, 0) 489 | 490 | self.assertWriteToReadOnlyPropertyFails('max_messages', 42) 491 | 492 | def test_property_max_message_size(self): 493 | """exercise MessageQueue.max_message_size""" 494 | self.assertGreaterEqual(self.mq.max_message_size, 0) 495 | 496 | self.assertWriteToReadOnlyPropertyFails('max_message_size', 42) 497 | 498 | def test_property_current_messages(self): 499 | """exercise MessageQueue.current_messages""" 500 | self.assertEqual(self.mq.current_messages, 0) 501 | 502 | self.mq.send('') 503 | self.assertEqual(self.mq.current_messages, 1) 504 | self.mq.send('') 505 | self.mq.send('') 506 | self.mq.send('') 507 | self.assertEqual(self.mq.current_messages, 4) 508 | self.mq.receive() 509 | self.assertEqual(self.mq.current_messages, 3) 510 | self.mq.receive() 511 | self.assertEqual(self.mq.current_messages, 2) 512 | self.mq.receive() 513 | self.assertEqual(self.mq.current_messages, 1) 514 | self.mq.receive() 515 | self.assertEqual(self.mq.current_messages, 0) 516 | 517 | self.assertWriteToReadOnlyPropertyFails('current_messages', 42) 518 | 519 | def test_block_flag_default_value_and_writability(self): 520 | """test that the block flag is True by default and can be changed""" 521 | mq = posix_ipc.MessageQueue(None, posix_ipc.O_CREX) 522 | 523 | self.assertTrue(mq.block) 524 | 525 | # Toggle. I expect no errors. It's good to test this on a queue 526 | # that's only used for this particular test. I would rather have 527 | # all the other tests execute using the default value of block 528 | # on self.mq. 529 | mq.block = False 530 | mq.block = True 531 | 532 | mq.close() 533 | mq.unlink() 534 | 535 | def test_block_flag_false(self): 536 | """test blocking behavior when flag is false""" 537 | mq = posix_ipc.MessageQueue(None, posix_ipc.O_CREX, max_messages=3) 538 | 539 | mq.block = False 540 | 541 | # Queue is empty, so receive() should immediately raise BusyError 542 | start = time.time() 543 | self.assertRaises(posix_ipc.BusyError, mq.receive, 10) 544 | elapsed = time.time() - start 545 | 546 | # Not only should receive() have raised BusyError, it should have 547 | # done so "immediately". I don't insist on extreme precision since 548 | # OS-level task switching might mean that elapsed time is not 549 | # vanishingly small as one might expect under most circumstances. 550 | self.assertTrue(elapsed < 1.0) 551 | 552 | # Now test send() the same way. 553 | mq.send(' ') 554 | mq.send(' ') 555 | mq.send(' ') 556 | 557 | # Queue is now full. 558 | start = time.time() 559 | self.assertRaises(posix_ipc.BusyError, mq.send, ' ', 10) 560 | elapsed = time.time() - start 561 | self.assertTrue(elapsed < 1.0) 562 | 563 | mq.close() 564 | mq.unlink() 565 | 566 | 567 | if __name__ == '__main__': 568 | unittest.main() 569 | -------------------------------------------------------------------------------- /tests/test_module.py: -------------------------------------------------------------------------------- 1 | # Python imports 2 | import unittest 3 | import os 4 | import resource 5 | 6 | # Project imports 7 | import posix_ipc 8 | 9 | from . import base as tests_base 10 | 11 | ONE_MILLION = 1000000 12 | 13 | 14 | class TestModule(tests_base.Base): 15 | """Exercise the posix_ipc module-level functions and constants""" 16 | def test_constant_values(self): 17 | """test that constants are what I expect""" 18 | self.assertEqual(posix_ipc.O_CREAT, os.O_CREAT) 19 | self.assertEqual(posix_ipc.O_EXCL, os.O_EXCL) 20 | self.assertEqual(posix_ipc.O_CREX, posix_ipc.O_CREAT | posix_ipc.O_EXCL) 21 | self.assertEqual(posix_ipc.O_TRUNC, os.O_TRUNC) 22 | 23 | self.assertEqual(posix_ipc.PAGE_SIZE, resource.getpagesize()) 24 | 25 | self.assertIn(posix_ipc.SEMAPHORE_TIMEOUT_SUPPORTED, (True, False)) 26 | self.assertIn(posix_ipc.SEMAPHORE_VALUE_SUPPORTED, (True, False)) 27 | 28 | self.assertGreaterEqual(posix_ipc.SEMAPHORE_VALUE_MAX, 1) 29 | 30 | self.assertIn(posix_ipc.MESSAGE_QUEUES_SUPPORTED, (True, False)) 31 | 32 | if posix_ipc.MESSAGE_QUEUES_SUPPORTED: 33 | self.assertGreaterEqual(posix_ipc.QUEUE_MESSAGES_MAX_DEFAULT, 1) 34 | self.assertGreaterEqual(posix_ipc.QUEUE_MESSAGE_SIZE_MAX_DEFAULT, 1) 35 | self.assertGreaterEqual(posix_ipc.QUEUE_PRIORITY_MAX, 0) 36 | 37 | if hasattr(posix_ipc, 'USER_SIGNAL_MIN'): 38 | self.assertGreaterEqual(posix_ipc.USER_SIGNAL_MIN, 1) 39 | if hasattr(posix_ipc, 'USER_SIGNAL_MAX'): 40 | self.assertGreaterEqual(posix_ipc.USER_SIGNAL_MAX, 1) 41 | 42 | self.assertTrue(isinstance(posix_ipc.VERSION, str)) 43 | 44 | def test_unlink_semaphore(self): 45 | """Exercise unlink_semaphore""" 46 | sem = posix_ipc.Semaphore(None, posix_ipc.O_CREX) 47 | posix_ipc.unlink_semaphore(sem.name) 48 | sem.close() 49 | self.assertRaises(posix_ipc.ExistentialError, posix_ipc.Semaphore, 50 | sem.name) 51 | 52 | def test_unlink_shared_memory(self): 53 | """Exercise unlink_shared_memory""" 54 | mem = posix_ipc.SharedMemory(None, posix_ipc.O_CREX, size=1024) 55 | mem.close_fd() 56 | posix_ipc.unlink_shared_memory(mem.name) 57 | self.assertRaises(posix_ipc.ExistentialError, posix_ipc.SharedMemory, 58 | mem.name) 59 | 60 | if posix_ipc.MESSAGE_QUEUES_SUPPORTED: 61 | def test_unlink_message_queue(self): 62 | """Exercise unlink_message_queue""" 63 | mq = posix_ipc.MessageQueue(None, posix_ipc.O_CREX) 64 | posix_ipc.unlink_message_queue(mq.name) 65 | mq.close() 66 | self.assertRaises(posix_ipc.ExistentialError, 67 | posix_ipc.MessageQueue, mq.name) 68 | 69 | def test_constant_queue_priority_max(self): 70 | """Test that QUEUE_PRIORITY_MAX is reported correctly""" 71 | mq = posix_ipc.MessageQueue(None, posix_ipc.O_CREX) 72 | 73 | if posix_ipc.QUEUE_PRIORITY_MAX < ONE_MILLION: 74 | for sent_priority in range(posix_ipc.QUEUE_PRIORITY_MAX + 1): 75 | mq.send('', priority=sent_priority) 76 | msg, received_priority = mq.receive() 77 | self.assertEqual(sent_priority, received_priority) 78 | # else: 79 | # QUEUE_PRIORITY_MAX is probably LONG_MAX or larger and 80 | # testing every value will take too long. 81 | 82 | self.assertRaises(ValueError, mq.send, '', 83 | priority=posix_ipc.QUEUE_PRIORITY_MAX + 1) 84 | 85 | mq.unlink() 86 | mq.close() 87 | 88 | def test_errors(self): 89 | self.assertTrue(issubclass(posix_ipc.Error, Exception)) 90 | self.assertTrue(issubclass(posix_ipc.SignalError, posix_ipc.Error)) 91 | self.assertTrue(issubclass(posix_ipc.PermissionsError, posix_ipc.Error)) 92 | self.assertTrue(issubclass(posix_ipc.ExistentialError, posix_ipc.Error)) 93 | self.assertTrue(issubclass(posix_ipc.BusyError, posix_ipc.Error)) 94 | 95 | 96 | if __name__ == '__main__': 97 | unittest.main() 98 | -------------------------------------------------------------------------------- /tests/test_semaphores.py: -------------------------------------------------------------------------------- 1 | # Python imports 2 | import unittest 3 | from unittest import skipUnless 4 | import datetime 5 | 6 | # Project imports 7 | import posix_ipc 8 | 9 | from . import base as tests_base 10 | 11 | 12 | # N_RELEASES is the number of times release() is called in test_release() 13 | N_RELEASES = 1000000 # 1 million 14 | 15 | 16 | class SemaphoreTestBase(tests_base.Base): 17 | """base class for Semaphore test classes""" 18 | def setUp(self): 19 | self.sem = posix_ipc.Semaphore(None, posix_ipc.O_CREX, initial_value=1) 20 | 21 | def tearDown(self): 22 | if self.sem: 23 | self.sem.unlink() 24 | 25 | def assertWriteToReadOnlyPropertyFails(self, property_name, value): 26 | """test that writing to a readonly property raises TypeError""" 27 | tests_base.Base.assertWriteToReadOnlyPropertyFails(self, self.sem, property_name, value) 28 | 29 | 30 | class TestSemaphoreCreation(SemaphoreTestBase): 31 | """Exercise stuff related to creating Semaphores""" 32 | def test_no_flags(self): 33 | """tests that opening a semaphore with no flags opens the existing 34 | semaphore and doesn't create a new semaphore""" 35 | sem_copy = posix_ipc.Semaphore(self.sem.name) 36 | self.assertEqual(self.sem.name, sem_copy.name) 37 | 38 | def test_o_creat_existing(self): 39 | """tests posix_ipc.O_CREAT to open an existing semaphore without 40 | O_EXCL""" 41 | sem_copy = posix_ipc.Semaphore(self.sem.name, posix_ipc.O_CREAT) 42 | 43 | self.assertEqual(self.sem.name, sem_copy.name) 44 | 45 | def test_o_creat_new(self): 46 | """tests posix_ipc.O_CREAT to create a new semaphore without O_EXCL""" 47 | # I can't pass None for the name unless I also pass O_EXCL. 48 | name = tests_base.make_name() 49 | 50 | # Note: this method of finding an unused name is vulnerable to a race 51 | # condition. It's good enough for test, but don't copy it for use in 52 | # production code! 53 | name_is_available = False 54 | while not name_is_available: 55 | try: 56 | sem = posix_ipc.Semaphore(name) 57 | sem.close() 58 | except posix_ipc.ExistentialError: 59 | name_is_available = True 60 | else: 61 | name = tests_base.make_name() 62 | 63 | sem = posix_ipc.Semaphore(name, posix_ipc.O_CREAT) 64 | 65 | self.assertIsNotNone(sem) 66 | 67 | sem.unlink() 68 | 69 | @unittest.skipIf(tests_base.HAS_FREEBSD_BUG_206396, tests_base.FREEBSD_BUG_206396_SKIP_MSG) 70 | def test_o_excl(self): 71 | """tests O_CREAT | O_EXCL prevents opening an existing semaphore""" 72 | self.assertRaises(posix_ipc.ExistentialError, posix_ipc.Semaphore, 73 | self.sem.name, posix_ipc.O_CREAT | posix_ipc.O_EXCL) 74 | 75 | @unittest.skipIf(tests_base.HAS_FREEBSD_BUG_206396, tests_base.FREEBSD_BUG_206396_SKIP_MSG) 76 | def test_o_crex(self): 77 | """tests O_CREX prevents opening an existing semaphore""" 78 | self.assertRaises(posix_ipc.ExistentialError, posix_ipc.Semaphore, 79 | self.sem.name, posix_ipc.O_CREX) 80 | 81 | def test_randomly_generated_name(self): 82 | """tests that the randomly-generated name works""" 83 | # This is tested implicitly elsewhere but I want to test it explicitly 84 | sem = posix_ipc.Semaphore(None, posix_ipc.O_CREX) 85 | self.assertIsNotNone(sem.name) 86 | 87 | self.assertEqual(sem.name[0], '/') 88 | self.assertGreaterEqual(len(sem.name), 2) 89 | sem.unlink() 90 | 91 | def test_name_as_bytes(self): 92 | """Test that the name can be bytes.""" 93 | name = bytes(tests_base.make_name(), 'ASCII') 94 | sem = posix_ipc.Semaphore(name, posix_ipc.O_CREX) 95 | self.assertEqual(name, bytes(sem.name, 'ASCII')) 96 | sem.unlink() 97 | sem.close() 98 | 99 | # don't bother testing mode, it's ignored by the OS? 100 | 101 | @skipUnless(posix_ipc.SEMAPHORE_VALUE_SUPPORTED, "Requires Semaphore.value support") 102 | def test_default_initial_value(self): 103 | """tests that the initial value is 0 by default""" 104 | sem = posix_ipc.Semaphore(None, posix_ipc.O_CREX) 105 | self.assertEqual(sem.value, 0) 106 | sem.unlink() 107 | 108 | @skipUnless(posix_ipc.SEMAPHORE_VALUE_SUPPORTED, "Requires Semaphore.value support") 109 | def test_zero_initial_value(self): 110 | """tests that the initial value is 0 when assigned""" 111 | sem = posix_ipc.Semaphore(None, posix_ipc.O_CREX, initial_value=0) 112 | self.assertEqual(sem.value, 0) 113 | sem.unlink() 114 | 115 | @skipUnless(posix_ipc.SEMAPHORE_VALUE_SUPPORTED, "Requires Semaphore.value support") 116 | def test_nonzero_initial_value(self): 117 | """tests that the initial value is non-zero when assigned""" 118 | sem = posix_ipc.Semaphore(None, posix_ipc.O_CREX, initial_value=42) 119 | self.assertEqual(sem.value, 42) 120 | sem.unlink() 121 | 122 | def test_kwargs(self): 123 | """ensure init accepts keyword args as advertised""" 124 | # mode 0x180 = 0600. Octal is difficult to express in Python 2/3 compatible code. 125 | sem = posix_ipc.Semaphore(None, flags=posix_ipc.O_CREX, mode=0x180, initial_value=0) 126 | sem.unlink() 127 | 128 | 129 | class TestSemaphoreAquisitionAndRelease(SemaphoreTestBase): 130 | """Exercise acquiring & releasing semaphores""" 131 | def test_simple_acquisition(self): 132 | """tests that acquisition works""" 133 | self.sem.acquire() 134 | 135 | # test acquisition failures 136 | # def test_acquisition_no_timeout(self): 137 | # FIXME 138 | # This is hard to test since it should wait infinitely. Probably the way 139 | # to do it is to spawn another process that holds the semaphore for 140 | # maybe 10 seconds and have this process wait on it. That's complicated 141 | # and not a really great test. 142 | 143 | def test_acquisition_zero_timeout(self): 144 | """tests that acquisition w/timeout=0 implements non-blocking 145 | behavior""" 146 | # Should not raise an error 147 | self.sem.acquire(0) 148 | with self.assertRaises(posix_ipc.BusyError): 149 | self.sem.acquire(0) 150 | 151 | @skipUnless(posix_ipc.SEMAPHORE_TIMEOUT_SUPPORTED, "Requires Semaphore timeout support") 152 | def test_acquisition_nonzero_int_timeout(self): 153 | """tests that acquisition w/timeout=an int is reasonably accurate""" 154 | # Should not raise an error 155 | self.sem.acquire(0) 156 | 157 | # This should raise a busy error 158 | wait_time = 1 159 | start = datetime.datetime.now() 160 | with self.assertRaises(posix_ipc.BusyError): 161 | self.sem.acquire(wait_time) 162 | end = datetime.datetime.now() 163 | actual_delta = end - start 164 | expected_delta = datetime.timedelta(seconds=wait_time) 165 | 166 | delta = actual_delta - expected_delta 167 | 168 | self.assertEqual(delta.days, 0) 169 | self.assertEqual(delta.seconds, 0) 170 | # I don't want to test microseconds because that granularity 171 | # isn't under the control of this module. 172 | 173 | @skipUnless(posix_ipc.SEMAPHORE_TIMEOUT_SUPPORTED, "Requires Semaphore timeout support") 174 | def test_acquisition_nonzero_float_timeout(self): 175 | """tests that acquisition w/timeout=a float is reasonably accurate""" 176 | # Should not raise an error 177 | self.sem.acquire(0) 178 | 179 | # This should raise a busy error 180 | wait_time = 1.5 181 | start = datetime.datetime.now() 182 | with self.assertRaises(posix_ipc.BusyError): 183 | self.sem.acquire(wait_time) 184 | end = datetime.datetime.now() 185 | actual_delta = end - start 186 | expected_delta = datetime.timedelta(seconds=wait_time) 187 | 188 | delta = actual_delta - expected_delta 189 | 190 | self.assertEqual(delta.days, 0) 191 | self.assertEqual(delta.seconds, 0) 192 | # I don't want to test microseconds because that granularity 193 | # isn't under the control of this module. 194 | 195 | def test_release(self): 196 | """tests that release works""" 197 | # Not only does it work, I can do it as many times as I want! I had 198 | # tried some code that called release() SEMAPHORE_VALUE_MAX times, but 199 | # on platforms where that's ~2 billion, the test takes too long to run. 200 | # So I'll stick to a lower (but still very large) number of releases. 201 | n_releases = min(N_RELEASES, posix_ipc.SEMAPHORE_VALUE_MAX - 1) 202 | for i in range(n_releases): 203 | self.sem.release() 204 | 205 | def test_context_manager(self): 206 | """tests that context manager acquire/release works""" 207 | with self.sem as sem: 208 | if posix_ipc.SEMAPHORE_VALUE_SUPPORTED: 209 | self.assertEqual(sem.value, 0) 210 | with self.assertRaises(posix_ipc.BusyError): 211 | sem.acquire(0) 212 | 213 | if posix_ipc.SEMAPHORE_VALUE_SUPPORTED: 214 | self.assertEqual(sem.value, 1) 215 | 216 | # Should not raise an error. 217 | sem.acquire(0) 218 | 219 | 220 | class TestSemaphoreDestruction(SemaphoreTestBase): 221 | def test_close_and_unlink(self): 222 | """tests that sem.close() and sem.unlink() work""" 223 | # sem.close() is hard to test since subsequent use of the semaphore 224 | # after sem.close() is undefined. All I can think of to do is call it 225 | # and note that it does not fail. Also, it allows sem.unlink() to 226 | # tell the OS to delete the semaphore entirely, so it makes sense 227 | # to test them together. 228 | self.sem.unlink() 229 | self.sem.close() 230 | self.assertRaises(posix_ipc.ExistentialError, posix_ipc.Semaphore, 231 | self.sem.name) 232 | # Wipe this out so that self.tearDown() doesn't crash. 233 | self.sem = None 234 | 235 | 236 | class TestSemaphorePropertiesAndAttributes(SemaphoreTestBase): 237 | def test_property_name(self): 238 | """exercise Semaphore.name""" 239 | self.assertGreaterEqual(len(self.sem.name), 2) 240 | 241 | self.assertEqual(self.sem.name[0], '/') 242 | 243 | self.assertWriteToReadOnlyPropertyFails('name', 'hello world') 244 | 245 | @skipUnless(posix_ipc.SEMAPHORE_VALUE_SUPPORTED, "Requires Semaphore.value support") 246 | def test_property_value(self): 247 | """exercise Semaphore.value if possible""" 248 | # test read, although this has been tested very thoroughly above 249 | self.assertEqual(self.sem.value, 1) 250 | 251 | self.assertWriteToReadOnlyPropertyFails('value', 42) 252 | 253 | 254 | if __name__ == '__main__': 255 | unittest.main() 256 | --------------------------------------------------------------------------------