-
-
Notifications
You must be signed in to change notification settings - Fork 30k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
bpo-35813: Tests and docs for shared_memory #11816
Conversation
…luminated by existing test run on Linux.
…ubmodule, name refactoring for greater clarity.
… switched away from exclusively registered functions to some explicit methods on SharedMemoryManager.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good job!!! Please consider review my comments. Please review the CI problems.
|
||
This module provides a class, :class:`SharedMemory`, for the allocation | ||
and management of shared memory to be accessed by one or more processes | ||
on a multicore or symmetric multiprocessor (SMP) machine. To assist with |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
the double whitepace on machine. To assist
if for more readable?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In the resulting html, whether one or two spaces are in the source only one space is presented in the browser. The convention of using two spaces predates patterns of using only one space after a period. As far as I know, both are "correct". Thankfully the output is the same either way.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I did not go looking for this but I accidentally stumbled across it in the dev guide (see devguide.python.org for lots more such goodies):
A sentence-ending period may be followed by one or two spaces;
while reST ignores the second space, it is customarily put in
by some users, for example to aid Emacs’ auto-fill mode.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh great thanks for the clarification. I had the idea that double space is used just on docstring.
|
||
>>> from multiprocessing import shared_memory | ||
>>> shm_a = shared_memory.SharedMemory(None, size=10) | ||
>>> type(shm_b.buf) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Where do you define shm_b?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Great spotting that missed detail! Fixed in commit da7731d.
>>> len(buffer) | ||
10 | ||
>>> buffer[:4] = bytearray([22, 33, 44, 55]) # Modify multiple at once | ||
>>> buffer[4] = 100 # Modify single byte at a time |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't know if pep8 will fail here. Because it say you that you need have two whitespaces.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I believe this physical alignment of two related comments helps readability significantly. To not do so would make the example much less readable. Pep8 encourages considering this in particular.
>>> array.array('b', shm_b.buf[:5]) # Copy the data into a new array.array | ||
array('b', [22, 33, 44, 55, 100]) | ||
>>> shm_b.buf[:5] = b'howdy' # Modify via shm_b using bytes | ||
>>> bytes(shm_a.buf[:5]) # Access via shm_a |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
same here
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same comment as on the prior; I believe this significantly helps readability in a situation where such help is needed.
|
||
|
||
The following example demonstrates a practical use of the :class:`SharedMemory` | ||
class with `NumPy arrays <https://www.numpy.org/>`_, accessing the |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't know if is necessary show a sample with NumPy, it's just an opinion
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The use of NumPy arrays with shared memory is anticipated to be one of the most popular use cases for working with shared memory. Demonstrating this combination is likely very important to a large number of people.
|
||
A call to :meth:`~multiprocessing.managers.BaseManager.start` on a | ||
:class:`SharedMemoryManager` instance causes a new process to be started. | ||
This new process's sole purpose is to manage the life cycle |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
process'
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
After double-checking with a technical editor just now, I have been assured that either is correct.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Great!
Lib/multiprocessing/shared_memory.py
Outdated
@@ -8,34 +8,120 @@ | |||
|
|||
from functools import reduce | |||
import mmap | |||
from .managers import DictProxy, SyncManager, Server | |||
from .managers import dispatch, BaseManager, Server, State, ProcessError, \ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What do you think about use ()
on imports? This way you avoid use \
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I like both styles. At the moment, I think it best to stay consistent with the pattern that already exists in multiprocessing and its submodules.
…t supporting all registered functions on SyncManager.
*name* is the unique name for the requested shared memory, specified as | ||
a string. If ``None`` is supplied for the name, a new shared memory | ||
block with a novel name will be created without needing to also specify | ||
``flags``. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since it appears name
is optional, why not make it a kwarg?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Moved this topic to b.p.o. starting with msg335660 there.
exception will be raised. To request the creation of a new shared | ||
memory block, set to ``O_CREX``. To request the optional creation of a | ||
new shared memory block or attach to an existing one by the same name, | ||
set to ``O_CREAT``. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It appears deciding whether to use O_CREX
/ O_CREAT
can be implemented as an internal detail based on whether the name is provided or not. Are there other use cases for flags
argument?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Moved this topic to b.p.o. starting with msg335660 there.
set to ``O_CREAT``. | ||
|
||
*mode* controls user/group/all-based read/write permissions on the | ||
shared memory block. Its specification is not enforceable on all platforms. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is mode
intended to work like the mode of UNIX file? (I see it defaults to 0o600
so it appears it does). What is the use case for it?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Moved this topic to b.p.o. starting with msg335660 there.
copying of data. | ||
|
||
|
||
.. class:: SharedMemory(name, flags=None, mode=0o600, size=0, read_only=False) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If I understand your code samples it seems this class/object can be returned via the SharedMemoryManager
as in:
>>> mgr = SharedMemoryManager()
>>> mgr.start()
>>> raw_shm = smm.SharedMemory(size=128)
As such, what's the use case for using SharedMemory
directly? Also, what's the use case for the name
parameter? I understand it you can use it for attaching to a pre-existing instance of SharedMemory
, but this kind of diverges from the base SyncManager
which tipically allows sharing objects by passing them as arguments for the worker. E.g., with a plain dict:
import multiprocessing
import multiprocessing.managers
def worker(d):
print(d) # prints {'foo': 1}
d['bar'] = 2 # I can also change the dict from here (child process)
manager = multiprocessing.managers.SyncManager()
manager.start()
d = manager.dict()
d['foo'] = 1
proc = multiprocessing.Process(target=worker, args=(d, ))
proc.start()
proc.join()
I wonder if this (the name
) can be made transparent (aka an implementation detail).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Moved this topic to b.p.o. starting with msg335622 there.
|
||
*read_only* controls whether a shared memory block is to be available | ||
for only reading or for both reading and writing. Its specification is | ||
not enforceable on all platforms. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You should mention what platforms implement this and what happens when it's not and True
is passed. Also, since this appears not portable across platform, perhaps it's better to not support it at all.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Agreed -- with the simpler API introduced in commit 0d3d06f, we no longer expose users to such complications.
Lib/test/_test_multiprocessing.py
Outdated
|
||
def setUp(self): | ||
if not HAS_SHMEM: | ||
self.skipTest("requires multiprocessing.shared_memory") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You can decorate the class instead
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Most of the other tests in _test_multiprocessing.py
use this (potentially older) style rather than the decorator, but this is no longer universally true. I will adopt the decorator style for new tests added to the testing module. Changed in commit 1e5341e as well.
Lib/test/_test_multiprocessing.py
Outdated
|
||
finally: | ||
# Prevent test failures from leading to a dangling segment. | ||
sms.unlink() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You could use self.addCleanup(sms.unlink)
right after creating sms
, spare this long try/finally block and save one indentation.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good thought. Changed in commit 1e5341e.
Lib/test/_test_multiprocessing.py
Outdated
sms.close() | ||
|
||
finally: | ||
sms.unlink() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You could use self.addCleanup(sms.unlink)
right after creating sms
, spare this long try/finally block and save one indentation.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good thought -- same as prior comment.
Lib/test/_test_multiprocessing.py
Outdated
sms_uno.close() | ||
|
||
finally: | ||
sms_uno.unlink() # A second shm_unlink() call is bad. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
also here (could use addCleanup
)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actually, this one needs to stay as it is, because this second call to unlink()
is needed to trigger the exception that we are testing for.
|
||
def test_joinable_queue(self): | ||
self.test_queue("JoinableQueue") | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't understand why you need to move these 2 methods from the previous tests. The logic appears split between the mixing class and this one for no apparent reason (but I may be missing it). If you need a different logic for certain TestSharedMemoryManagerTypes
methods/tests you can simply override them (e.g. if you want to skip them).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Both Queue
and JoinableQueue
are implemented as distributed shared memory (a queue.Queue
object existing in one process and an AutoProxy
object existing in the local process). For the same reasons mentioned in my b.p.o. comment, I believe it is much clearer to not include these on SharedMemoryManager
by default and to instead document how they may be added on-demand with a single line of code (by calling register()
).
There is also the potential for a POSIX shared memory message queue (not currently implemented in shared_memory.py). This is also something worth considering for the future.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Question: if we expose them they are not subject to the speedup, correct? Would it be the same as using them via the SyncManager
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Correct, exposing Queue
or JoinableQueue
here would not make them faster; they would be the same as if using them via SyncManager
.
We could simply encourage users to employ a SyncManager
anytime they want to use a Queue
or Lock
but this forces the creation of two managers with a server behind each and therefore two processes instead of only one. I think it will be easier to explain, "create one manager to oversee work and provide cleanup at the end."
Although my use of empty()
in this example is admittedly fragile, this example shows augmenting SyncManager
with Queue
is really only one line of code:
from multiprocessing import shared_memory
import queue
shared_memory.SharedMemoryManager.register('Queue', queue.Queue)
with shared_memory.SharedMemoryManager() as smm:
sl = smm.ShareableList("feb") # fast, zero-copy shared memory
q = smm.Queue() # slow, copy+message distributed shared memory
for letter in sl:
q.put(letter.upper())
while not q.empty():
print(q.get(timeout=1))
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I also think we should get rid of Queue, Lock, etc. Just for confirmation: ShareableList
and AsyncManager.Lock
can be safely mixed/used together, correct?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Agreed -- I have now gotten rid of Queue, Lock, etc. in commit 395709b.
Yes, confirmed: ShareableList
and SyncManager.Lock
can be safely mixed/used together.
I was sure you meant SyncManager
when your wrote AsyncManager
but I still went searching just to make sure there was not some AsyncManager
hiding inside asyncio
somewhere that I was not thinking of. =)
When you're done making the requested changes, leave the comment: And if you don't make the requested changes, you will be put in the comfy chair! |
…ne indentation, change to use decorator to skip test instead.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nice simplifications! This looks good to me.
Lib/multiprocessing/shared_memory.py
Outdated
import ctypes | ||
from ctypes import wintypes | ||
|
||
kernel32 = ctypes.windll.kernel32 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should be kernel32 = ctypes.WinDLL('kernel32', use_last_error=True)
. With ctypes.windll
, it's using a globally cached WinDLL
instance. This in turn caches function pointers, which leads to conflicting prototypes between packages. Also, use_last_error
is required for ctypes.get_last_error()
, which is more reliable than FFI-based ctypes.GetLastError()
.
Is this ctypes usage just temporary for experimenting until the API is finalized? Ultimately I hope this uses _winapi instead of ctypes.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks, eryksun -- you rock. Changed both in commit 868b83d.
I believe the API is close enough to being finalized that we can move this to _winapi now. I would very much appreciate your help reviewing that move, if I can talk you into that.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I have moved this over to use _winapi now, changed in commit 9d83b06.
If you are up for helping review this too, it would again be very much appreciated.
Lib/multiprocessing/shared_memory.py
Outdated
name | ||
) | ||
try: | ||
last_error_code = kernel32.GetLastError() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should call ctypes.get_last_error()
, which is being called elsewhere. But for it to work, we need use_last_error=True
, as mentioned above.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Strike that. kernel32.CreateFileMappingW._errcheck
is set to _errcheck_bool
, which should raise FileExistsError
here. Maybe this was added due to a misunderstanding. Using ctypes.windll.kernel32
renders the _errcheck
function useless, since ctypes.get_last_error
isn't enabled.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When kernel32.CreateFileMappingW
is passed the name of an existing anonymous memory block in this way, it succeeds and it does not raise FileExistsError
(_errcheck_bool
receives a non-zero result
value). This motivates the ctypes.get_last_error()
call to check for a return value of 183
.
This seems to match what is described on docs.microsoft.com, regarding the return value from CreateFileMappingA
:
If the object exists before the function call, the function
returns a handle to the existing object (with its current
size, not the specified size), and GetLastError returns
ERROR_ALREADY_EXISTS.
FWIW, this technique is leveraged in the Boost library for this same purpose. The following is a snippet from boost/interprocess/windows_shared_memory.hpp where create_file_mapping
is a wrapper for CreateFileMappingW
:
switch(type){
case ipcdetail::DoOpen:
m_handle = winapi::open_file_mapping(map_access, filename);
break;
case ipcdetail::DoCreate:
case ipcdetail::DoOpenOrCreate:
{
m_handle = winapi::create_file_mapping
( winapi::invalid_handle_value, protection, size, filename
, (winapi::interprocess_security_attributes*)perm.get_permissions());
}
break;
default:
{
error_info err = other_error;
throw interprocess_exception(err);
}
}
if(!m_handle || (type == ipcdetail::DoCreate && winapi::get_last_error() == winapi::error_already_exists)){
error_info err = system_error_code();
this->priv_close();
throw interprocess_exception(err);
}
I would be happier if CreateFileMappingW
behaved as you suggested it might.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
it succeeds and it does not raise
FileExistsError
(_errcheck_bool
receives a non-zeroresult
value). This motivates thectypes.get_last_error()
call to check for a return value of183
.
Sorry it slipped my mind that Windows normally uses the OBJ_OPENIF
disposition when creating a named kernel object. If this flag weren't used, then the underlying NtCreateSection
system call would fail with STATUS_OBJECT_NAME_COLLISION
, which maps to Windows ERROR_ALREADY_EXISTS
. Instead it succeeds, and CreateFileMapping
manually sets the last error. It's one of a small number of cases in which a WINAPI call succeeds with an error set.
Lib/multiprocessing/shared_memory.py
Outdated
PAGE_READONLY = 0x02 | ||
PAGE_EXECUTE_READWRITE = 0x04 | ||
INVALID_HANDLE_VALUE = -1 | ||
FILE_ALREADY_EXISTS = 183 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This constant shouldn't be necessary once other mistakes are corrected. But it should be named ERROR_ALREADY_EXISTS
if it stays. It's confusing to readers if we invent our own names for well-known error codes.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for catching this detail about the appropriate name. Changed in commit 868b83d (at least until it is no longer needed).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These have now been (re-)moved to _winapi per your later suggestion as part of commit 6878533.
Lib/multiprocessing/shared_memory.py
Outdated
try: | ||
last_error_code = _winapi.GetLastError() | ||
if last_error_code == _winapi.ERROR_ALREADY_EXISTS: | ||
raise FileExistsError(f"File exists: {name!r}") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It would be nice to include the errno
and winerror
values for completeness: FileExistsError(errno.EEXIST, os.strerror(errno.EEXIST), name, _winapi.ERROR_ALREADY_EXISTS)
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good point -- added in commit 6878533.
Lib/multiprocessing/shared_memory.py
Outdated
# Attempt to dynamically determine the existing named shared | ||
# memory block's size which is likely a multiple of mmap.PAGESIZE. | ||
try: | ||
h_map = _winapi.OpenFileMappingW(PAGE_READONLY, False, name) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should request FILE_MAP_READ
(4) access. PAGE_READONLY
(2) is a page protection for CreateFileMapping
. It happens to be the same constant value as FILE_MAP_WRITE
access.
CreateFileMapping
determines the desired access for the section object from the given page protection. OTOH, with OpenFileMapping
we have to supply the desired access explicitly.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for catching this. Changed to _winapi.FILE_MAP_READ
in commit 6878533.
Lib/multiprocessing/shared_memory.py
Outdated
except OSError: | ||
raise FileNotFoundError(name) | ||
try: | ||
p_buf = _winapi.MapViewOfFile(h_map, PAGE_READONLY, 0, 0, 0) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should request FILE_MAP_READ
(4) access.
It's the other way around in the underlying NT API NtMapViewOfSection
. It takes the desired page protection and determines the required section access from the protection. So MapViewOfFile[Ex]
actually reverses this to get the page protection in order to call NtMapViewOfSection
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also changed to _winapi.FILE_MAP_READ
in commit 6878533.
Lib/multiprocessing/shared_memory.py
Outdated
h_map = _winapi.CreateFileMappingW( | ||
INVALID_HANDLE_VALUE, | ||
_winapi.NULL, | ||
PAGE_EXECUTE_READWRITE, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why do use PAGE_EXECUTE_READWRITE
protection here? mmap defaults to PAGE_READWRITE
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I remember mentally debating this choice but not the details. Your point that mmap defaults to PAGE_READWRITE
should have dictated what was used but I somehow missed it.
Good catch -- changed to _winapi.PAGE_READWRITE
in commit 6878533.
Modules/_winapi.c
Outdated
Py_END_ALLOW_THREADS | ||
|
||
if (handle == NULL) { | ||
PyErr_SetFromWindowsErr(0); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Consider using PyErr_SetFromWindowsErrWithUnicodeFilename(0, name)
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done -- changed in commit 6878533.
Modules/_winapi.c
Outdated
@@ -464,6 +474,41 @@ _winapi_CreateFile_impl(PyObject *module, LPCTSTR file_name, | |||
return handle; | |||
} | |||
|
|||
/*[clinic input] | |||
_winapi.CreateFileMappingW -> HANDLE |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Personally, I'm not against using the real name with the "W" suffix. But thus far this module has used the macro names such as _winapi.CreateFileMapping
.
Actually, we don't define UNICODE
, and we have a few functions in _winapi that link to the ANSI version and use LPCTSTR
-> LPCSTR
(i.e. const char *
). This includes _winapi.CreateFile
. It should instead call CreateFileW
and use an LPCWSTR
name.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for making a point of this. I would prefer to stick with the established naming pattern unless there was a real reason otherwise. I have changed it to _winapi.CreateFileMapping
as you suggest and likewise dropped the "W" on _winapi.OpenFileMapping
in commit 6878533.
Lib/multiprocessing/shared_memory.py
Outdated
|
||
PAGE_READONLY = 0x02 | ||
PAGE_EXECUTE_READWRITE = 0x04 | ||
INVALID_HANDLE_VALUE = -1 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd add INVALID_HANDLE_VALUE
and the memory API constants to _winapi.
WINAPI_CONSTANT(F_HANDLE, INVALID_HANDLE_VALUE);
WINAPI_CONSTANT(F_DWORD, FILE_MAP_READ);
WINAPI_CONSTANT(F_DWORD, PAGE_READWRITE);
Maybe add the rest, too, because why not.
WINAPI_CONSTANT(F_DWORD, FILE_MAP_COPY);
WINAPI_CONSTANT(F_DWORD, FILE_MAP_WRITE);
WINAPI_CONSTANT(F_DWORD, FILE_MAP_EXECUTE);
WINAPI_CONSTANT(F_DWORD, FILE_MAP_ALL_ACCESS);
WINAPI_CONSTANT(F_DWORD, SEC_IMAGE);
WINAPI_CONSTANT(F_DWORD, SEC_RESERVE);
WINAPI_CONSTANT(F_DWORD, SEC_COMMIT);
WINAPI_CONSTANT(F_DWORD, SEC_NOCACHE);
WINAPI_CONSTANT(F_DWORD, SEC_WRITECOMBINE);
WINAPI_CONSTANT(F_DWORD, SEC_LARGE_PAGES);
WINAPI_CONSTANT(F_DWORD, PAGE_NOACCESS);
WINAPI_CONSTANT(F_DWORD, PAGE_READONLY);
WINAPI_CONSTANT(F_DWORD, PAGE_WRITECOPY);
WINAPI_CONSTANT(F_DWORD, PAGE_EXECUTE);
WINAPI_CONSTANT(F_DWORD, PAGE_EXECUTE_READ);
WINAPI_CONSTANT(F_DWORD, PAGE_EXECUTE_READWRITE);
WINAPI_CONSTANT(F_DWORD, PAGE_EXECUTE_WRITECOPY);
WINAPI_CONSTANT(F_DWORD, PAGE_GUARD);
WINAPI_CONSTANT(F_DWORD, PAGE_NOCACHE);
WINAPI_CONSTANT(F_DWORD, PAGE_WRITECOMBINE);
WINAPI_CONSTANT(F_DWORD, MEM_COMMIT);
WINAPI_CONSTANT(F_DWORD, MEM_RESERVE);
WINAPI_CONSTANT(F_DWORD, MEM_FREE);
WINAPI_CONSTANT(F_DWORD, MEM_PRIVATE);
WINAPI_CONSTANT(F_DWORD, MEM_MAPPED);
WINAPI_CONSTANT(F_DWORD, MEM_IMAGE);
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sounds plenty good to me -- all have been added to _winapi in commit 6878533. Thanks!
…owsNamedSharedMemory into one.
Modules/_winapi.c
Outdated
Py_END_ALLOW_THREADS | ||
|
||
if (handle == NULL) { | ||
PyErr_SetFromWindowsErr(0); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Use PyErr_SetFromWindowsErrWithUnicodeFilename(0, name)
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Changed in commit caf0a5d.
I should have thought to change this when you suggested similar on CreateFileMapping. Thanks!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Much better overall. I added some inline comments.
@@ -138,34 +130,99 @@ def unlink(self): | |||
pass | |||
|
|||
|
|||
class PosixSharedMemory(_PosixSharedMemory): | |||
# FreeBSD (and perhaps other BSDs) limit names to 14 characters. | |||
_SHM_SAFE_NAME_LENGTH = 14 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
FWIW this limit still exists and affects at least Linux, Solaris and AIX. I have code in psutil which takes this into account.
handle = INVALID_HANDLE_VALUE; | ||
} | ||
|
||
return handle; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Shouldn't you return NULL
instead of INVALID_HANDLE_VALUE
in case of error? It appears this way you'll return an int instead of causing an exception.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
HANDLE_return_converter
checks for INVALID_HANDLE_VALUE
with an error set. For NULL
it returns None
, which supports GetStdHandle
since a standard-handle value may be NULL
. Also, since an error must also be set, GetCurrentProcess
works with the same converter even though the value it returns is equivalent to INVALID_HANDLE_VALUE
.
It's a bit confusing if you're not aware of how it's implemented. It would probably be better to have a special handle type for functions that fail with INVALID_HANDLE_VALUE
, since by far the majority of WINAPI functions that return a handle (directly or as an out parameter) either use NULL
or FALSE
to indicate an error.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@eryksun: Would it make more sense to handle the change you're suggesting by opening a separate issue and PR for it?
I'm hesitant to make such a "code maintenance" change impacting unrelated code as part of this PR which is an "enhancement".
@eryksun: I will offer to help supply the patch if you will review it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's a minor change, but @zooba should approve it in a new issue before going forward with a PR. I'd rename the current type to HANDLE_I
and add a new HANDLE
type that uses NULL
for errors.
Another maintenance issue is removing the use of the TCHAR
API and unintentional linking with ANSI functions such as CreateFileA
, which I think would be fine to fix here if you want. The internal use of the affected functions hasn't had a problem thus far because they use ASCII strings, e.g. for pipe names, but it may eventually cause an issue.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's capture all of that in a new issue on b.p.o.
handle = INVALID_HANDLE_VALUE; | ||
} | ||
|
||
return handle; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
same here
Py_END_ALLOW_THREADS | ||
|
||
if (size_of_buf == 0) | ||
PyErr_SetFromWindowsErr(0); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
and here
Lib/multiprocessing/shared_memory.py
Outdated
called once (and only once) across all processes which have access | ||
to the shared memory block.""" | ||
if _USE_POSIX and self.name: | ||
_posixshmem.shm_unlink(self.name) | ||
|
||
|
||
encoding = "utf8" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is only used by ShareableList
. Why not make it a class attribute of that class instead? Also not sure if there are use-cases where one may want to change this (if not it's probably better to set it private).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Though it did not get included, the plan was to also supply a ShareableDict
and encoding
was to be used across both. Anticipating that a ShareableDict
will still land in the future, let's keep it outside the class but I agree it makes sense to make this private.
Changed to private in commit 12c097d.
Lib/multiprocessing/shared_memory.py
Outdated
|
||
|
||
class PosixSharedMemory(_PosixSharedMemory): | ||
O_CREX = os.O_CREAT | os.O_EXCL |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Consider making this private (_O_CREX
)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good point. Changed to private in commit 12c097d.
|
||
if (fd < 0) { | ||
if (!async_err) | ||
PyErr_SetFromErrnoWithFilenameObject(PyExc_OSError, path); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
looks like you want to Py_DECREF(path)
here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Perhaps I am missing something... It looks like PyUnicode_AsUTF8
does not create a new reference so there is nothing to decrement?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorry, you are right.
@applio: Please replace |
@@ -8,7 +8,8 @@ | |||
# Licensed to PSF under a Contributor Agreement. | |||
# | |||
|
|||
__all__ = [ 'BaseManager', 'SyncManager', 'BaseProxy', 'Token' ] | |||
__all__ = [ 'BaseManager', 'SyncManager', 'BaseProxy', 'Token', | |||
'SharedMemoryManager' ] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
According to this comment, sounds like adding SharedMemoryManager
to __all__
should be handled to the try...except
below.
PS: Nice work on this btw!
Tests and docs are now present. Discussion around the API for the SharedMemory class and the appropriateness of the shareable_wrap() function is requested in particular.
https://bugs.python.org/issue35813