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:
Lucian Petrut 2016-10-11 14:17:43 +03:00
parent 5304f3e04f
commit b9d5d0b18b
2 changed files with 36 additions and 23 deletions

View File

@ -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)

View File

@ -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)