Ensure GetLastError gets called in the right thread
Recently, we've started invoking Win32 API functions in different threads in order to avoid blocking other greenthreads. The only issue is that if a function fails, we're retrieving the error code by calling GetLastError from the original thread, thus retrieving wrong error codes. This change ensures that the subsequent error handling will be performed in the same thread as the invoked function. Change-Id: Id54b5bdb8e4561aaa42164b8c5d52a1a83da7149 Closes-Bug: #1632297
This commit is contained in:
parent
5304f3e04f
commit
b9d5d0b18b
@ -46,21 +46,15 @@ class Win32UtilsTestCase(base.BaseTestCase):
|
||||
wintypes=mock.DEFAULT,
|
||||
create=True).start()
|
||||
|
||||
@mock.patch.object(_utils, 'avoid_blocking_call')
|
||||
@mock.patch.object(win32utils.Win32Utils, 'get_error_message')
|
||||
@mock.patch.object(win32utils.Win32Utils, 'get_last_error')
|
||||
def _test_run_and_check_output(self, mock_get_last_err, mock_get_err_msg,
|
||||
mock_avoid_blocking_call,
|
||||
ret_val=0, expected_exc=None,
|
||||
**kwargs):
|
||||
self._ctypes_patcher.stop()
|
||||
|
||||
mock_func = mock.Mock()
|
||||
mock_func.return_value = ret_val
|
||||
mock_avoid_blocking_call.return_value = ret_val
|
||||
|
||||
eventlet_nonblocking_mode = kwargs.get(
|
||||
'eventlet_nonblocking_mode', True)
|
||||
|
||||
if expected_exc:
|
||||
self.assertRaises(expected_exc,
|
||||
@ -77,21 +71,14 @@ class Win32UtilsTestCase(base.BaseTestCase):
|
||||
**kwargs)
|
||||
self.assertEqual(ret_val, actual_ret_val)
|
||||
|
||||
if eventlet_nonblocking_mode:
|
||||
mock_avoid_blocking_call.assert_called_once_with(
|
||||
mock_func, mock.sentinel.arg, kwarg=mock.sentinel.kwarg)
|
||||
else:
|
||||
mock_func.assert_called_once_with(mock.sentinel.arg,
|
||||
kwarg=mock.sentinel.kwarg)
|
||||
mock_func.assert_called_once_with(mock.sentinel.arg,
|
||||
kwarg=mock.sentinel.kwarg)
|
||||
|
||||
return mock_get_last_err, mock_get_err_msg
|
||||
|
||||
def test_run_and_check_output(self):
|
||||
self._test_run_and_check_output()
|
||||
|
||||
def test_run_and_check_output_nonblocking_mode_disabled(self):
|
||||
self._test_run_and_check_output(eventlet_nonblocking_mode=False)
|
||||
|
||||
def test_run_and_check_output_fail_on_nonzero_ret_val(self):
|
||||
ret_val = 1
|
||||
|
||||
@ -148,6 +135,26 @@ class Win32UtilsTestCase(base.BaseTestCase):
|
||||
self.assertIsInstance(ex, exceptions.Win32Exception)
|
||||
self.assertIn(err_msg, ex.message)
|
||||
|
||||
@mock.patch.object(win32utils.Win32Utils, '_run_and_check_output')
|
||||
def test_run_and_check_output_eventlet_nb_mode_disabled(self, mock_helper):
|
||||
self._win32_utils.run_and_check_output(
|
||||
mock.sentinel.func,
|
||||
mock.sentinel.arg,
|
||||
eventlet_nonblocking_mode=False)
|
||||
mock_helper.assert_called_once_with(mock.sentinel.func,
|
||||
mock.sentinel.arg)
|
||||
|
||||
@mock.patch.object(_utils, 'avoid_blocking_call')
|
||||
def test_run_and_check_output_eventlet_nb_mode_enabled(self, mock_helper):
|
||||
self._win32_utils.run_and_check_output(
|
||||
mock.sentinel.func,
|
||||
mock.sentinel.arg,
|
||||
eventlet_nonblocking_mode=True)
|
||||
mock_helper.assert_called_once_with(
|
||||
self._win32_utils._run_and_check_output,
|
||||
mock.sentinel.func,
|
||||
mock.sentinel.arg)
|
||||
|
||||
def test_get_error_message(self):
|
||||
err_msg = self._win32_utils.get_error_message(mock.sentinel.err_code)
|
||||
|
||||
|
@ -37,7 +37,19 @@ class Win32Utils(object):
|
||||
self._kernel32_lib_func_opts = dict(error_on_nonzero_ret_val=False,
|
||||
ret_val_is_err_code=False)
|
||||
|
||||
def run_and_check_output(self, func, *args, **kwargs):
|
||||
def run_and_check_output(self, *args, **kwargs):
|
||||
eventlet_nonblocking_mode = kwargs.pop(
|
||||
'eventlet_nonblocking_mode', True)
|
||||
|
||||
if eventlet_nonblocking_mode:
|
||||
# We have to make sure that the invoked function as well as the
|
||||
# subsequent error handling are performed within the same thread.
|
||||
return _utils.avoid_blocking_call(
|
||||
self._run_and_check_output, *args, **kwargs)
|
||||
else:
|
||||
return self._run_and_check_output(*args, **kwargs)
|
||||
|
||||
def _run_and_check_output(self, func, *args, **kwargs):
|
||||
"""Convenience helper method for running Win32 API methods."""
|
||||
kernel32_lib_func = kwargs.pop('kernel32_lib_func', False)
|
||||
if kernel32_lib_func:
|
||||
@ -63,13 +75,7 @@ class Win32Utils(object):
|
||||
# message table.
|
||||
error_msg_src = kwargs.pop('error_msg_src', {})
|
||||
|
||||
eventlet_nonblocking_mode = kwargs.pop(
|
||||
'eventlet_nonblocking_mode', True)
|
||||
|
||||
if eventlet_nonblocking_mode:
|
||||
ret_val = _utils.avoid_blocking_call(func, *args, **kwargs)
|
||||
else:
|
||||
ret_val = func(*args, **kwargs)
|
||||
ret_val = func(*args, **kwargs)
|
||||
|
||||
func_failed = (error_on_nonzero_ret_val and ret_val) or (
|
||||
ret_val in error_ret_vals)
|
||||
|
Loading…
Reference in New Issue
Block a user