Change class variables to thread local variables#10833
Change class variables to thread local variables#10833piiswrong merged 15 commits intoapache:masterfrom
Conversation
| gpu(1) | ||
| """ | ||
| # static class variable | ||
| default_ctx = None |
There was a problem hiding this comment.
This will break a lot of existing code. Is this change intended for 2.0?
There was a problem hiding this comment.
I don't think this was meant to be public API. current_context() is used for accessing this.
There was a problem hiding this comment.
@piiswrong told me that default_ctx, devtype2str and devstr2type were not meant to be public API. This will also be renamed to _default_ctx. Users are expected to use current_context or default_context in test_utils. I agree though that this will break some existing code. I was of the opinion that the release after 1.2 is 2.0 but I am not sure.
There was a problem hiding this comment.
If there are users who depend on this, we can make a backward compatible wrapper with classmethod and property:https://stackoverflow.com/questions/1697501/python-staticmethod-with-property
There was a problem hiding this comment.
_default_ctx = threading.local()
@property
@classmethod
def default_ctx():
return _default_ctx.value
| def __exit__(self, ptype, value, trace): | ||
| self.load_local_var() | ||
| else: | ||
| class ThreadLocalRegistry(object, metaclass=ThreadLocalRegistryMeta): |
There was a problem hiding this comment.
This looks really complicated. why do you have to use inheritance? Isn't composition better?
There was a problem hiding this comment.
Inheritance was used since the thread local variables need to still be bound to the class rather than the object.
| class ThreadLocalRegistry(object, metaclass=ThreadLocalRegistryMeta): | ||
| """Structure that holds threadlocal variables and sets context manager""" | ||
|
|
||
| def __init__(self, name): |
There was a problem hiding this comment.
The name here is the name of the threadlocal variable for example default_ctx.
I will add an empty or None check here, to prevent from being misused.
| kwargs | ||
| The attributes to set for all symbol creations in the scope. | ||
| """ | ||
| current = None |
There was a problem hiding this comment.
_current = threading.local()
|
|
||
| def __enter__(self): | ||
| # pylint: disable=protected-access | ||
| self._old_scope = AttrScope.current |
There was a problem hiding this comment.
self._old_scope = AttrScope.current.value
|
|
||
| def __exit__(self, ptype, value, trace): | ||
| assert self._old_scope | ||
| AttrScope.current = self._old_scope |
There was a problem hiding this comment.
AttrScope.current.value = self._old_scope
| gpu(1) | ||
| """ | ||
| # static class variable | ||
| default_ctx = None |
There was a problem hiding this comment.
_default_ctx = threading.local()
@property
@classmethod
def default_ctx():
return _default_ctx.value
91c1737 to
b4996ac
Compare
| def __enter__(self): | ||
| self._old_ctx = Context.default_ctx | ||
| Context.default_ctx = self | ||
| if not hasattr(Context._default_ctx, "value"): |
There was a problem hiding this comment.
why do you need this? Isn't it set with https://github.com/apache/incubator-mxnet/pull/10833/files#diff-1a5e06031378f44204fd1da1fffc0b07R145
There was a problem hiding this comment.
what about a situation like below:
import threading
import mxnet as mx
def test_context():
ctx_list = []
def f():
mx.context.Context.default_ctx
ctx_list.append(Context.default_ctx)
thread = threading.Thread(target=f)
thread.start()
thread.join()
if __name__ == '__main__':
test_context()
Without hasattr this doesn't work.
| def create(prefix, params, hint): | ||
| """Creates prefix and params for new `Block`.""" | ||
| current = _BlockScope._current | ||
| current = getattr(_BlockScope._current, "value", None) |
There was a problem hiding this comment.
initialize _BlockScope._current.value to None globally and use _BlockScope._current.value directly here?
| def __enter__(self): | ||
| self._old_manager = NameManager.current | ||
| NameManager.current = self | ||
| if not hasattr(NameManager._current, "value"): |
| warnings.warn("NameManager.current has been deprecated. " | ||
| "It is advised to use the `with` statement with NameManager.", | ||
| DeprecationWarning) | ||
| if not hasattr(NameManager._current, "value"): |
| ctx : Context, optional | ||
| An optional device context. | ||
| Defaults to the current default context (``mxnet.Context.default_ctx``). | ||
| Defaults to the current default context (``mxnet.Context._default_ctx.value``). |
There was a problem hiding this comment.
This is public API. change to mxnet.current_context
| # pylint: disable=protected-access | ||
| self._old_scope = AttrScope.current | ||
| attr = AttrScope.current._attr.copy() | ||
| if not hasattr(AttrScope._current, "value"): |
|
Please remove the hasatter/getattr statements. Otherwise LGTM |
|
Recording some of the offline conversation: Initially the inheritance and metaclasses was for allowing code reuse of thread local code easily for multiple modules. This code was a bit complicated to understand so we moved the |
* Change to simpler implementation * Add property * Remove pdb * Add support for setter and getter * fix issues * Add warnings * Add thread local unittest and tlocal race condition * Fix pylint * Use current_context instead of _default_ctx * Use current_context * Fix race condition * Fix thread local test * Change to current_context
* Change to simpler implementation * Add property * Remove pdb * Add support for setter and getter * fix issues * Add warnings * Add thread local unittest and tlocal race condition * Fix pylint * Use current_context instead of _default_ctx * Use current_context * Fix race condition * Fix thread local test * Change to current_context
* Change to simpler implementation * Add property * Remove pdb * Add support for setter and getter * fix issues * Add warnings * Add thread local unittest and tlocal race condition * Fix pylint * Use current_context instead of _default_ctx * Use current_context * Fix race condition * Fix thread local test * Change to current_context
@szha Is this true? I find numerous instances where Python2-only builtins like basestring, long, reload, unicode, xrange are used. Do we need try/except blocks to define each of these or can we use six.moves? |
|
@cclauss I'd probably describe this as "six is not necessary", as we already handle most of the import compatibility in base.py where we use the python version to decide the definition, and we've been careful about not using version-specific features in our code base (e.g. we have both py2 and py3 in CI). Thus, adding six would mostly just benefit code that's outside of our code base, such as the examples, in which case the dependency is not managed by mxnet and authors are free to choose. |
Description
There are many places where class variables which end up modifying the state from multiple threads and causing issues like the one here: #9920 .
This PR is currently WIP because more investigation is required on
test_multi_worker_forked_data_loaderandtest_laop_3(able to reproduce this test failure without my changes also). Also, more tests need to be added.@piiswrong
Checklist
Essentials
Please feel free to remove inapplicable items for your PR.
Changes
Comments