Skip to content

Commit 6dda5b5

Browse files
The mode of python-oracledb is now fixed only after a
call to "oracledb.init_oracle_client()", "oracledb.connect()" or "oracledb.create_pool()" has completed successfully (#44).
1 parent 0a59085 commit 6dda5b5

File tree

7 files changed

+215
-166
lines changed

7 files changed

+215
-166
lines changed

doc/src/release_notes.rst

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,10 @@ Common Changes
3232
#) Enhanced type checking
3333
(`issue 54 <https://github.com/oracle/python-oracledb/issues/54>`__),
3434
(`issue 60 <https://github.com/oracle/python-oracledb/issues/60>`__).
35+
#) The mode of python-oracledb is now fixed only after a call to
36+
:meth:`oracledb.init_oracle_client()`, :meth:`oracledb.connect()` or
37+
:meth:`oracledb.create_pool()` has completed successfully
38+
(`issue 44 <https://github.com/oracle/python-oracledb/issues/44>`__).
3539
#) Improved test suite and documentation.
3640

3741

src/oracledb/connection.py

Lines changed: 45 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -99,51 +99,52 @@ def __init__(self,
9999
self._impl = None
100100

101101
# determine if thin mode is being used
102-
thin = driver_mode.check_and_return_mode()
103-
104-
# determine which connection parameters to use
105-
if params is None:
106-
params_impl = base_impl.ConnectParamsImpl()
107-
elif not isinstance(params, ConnectParams):
108-
errors._raise_err(errors.ERR_INVALID_CONNECT_PARAMS)
109-
else:
110-
params_impl = params._impl.copy()
111-
if kwargs:
112-
params_impl.set(kwargs)
113-
if dsn is not None:
114-
dsn = params_impl.parse_dsn(dsn, thin)
115-
if dsn is None:
116-
dsn = params_impl.get_connect_string()
117-
118-
# see if connection is being acquired from a pool
119-
if pool is None:
120-
pool_impl = None
121-
elif not isinstance(pool, pool_module.ConnectionPool):
122-
message = "pool must be an instance of oracledb.ConnectionPool"
123-
raise TypeError(message)
124-
else:
125-
pool._verify_open()
126-
pool_impl = pool._impl
127-
128-
# create thin or thick implementation object
129-
if thin:
130-
if pool is not None:
131-
impl = pool_impl.acquire(params_impl)
102+
with driver_mode.get_manager() as mode_mgr:
103+
thin = mode_mgr.thin
104+
105+
# determine which connection parameters to use
106+
if params is None:
107+
params_impl = base_impl.ConnectParamsImpl()
108+
elif not isinstance(params, ConnectParams):
109+
errors._raise_err(errors.ERR_INVALID_CONNECT_PARAMS)
110+
else:
111+
params_impl = params._impl.copy()
112+
if kwargs:
113+
params_impl.set(kwargs)
114+
if dsn is not None:
115+
dsn = params_impl.parse_dsn(dsn, thin)
116+
if dsn is None:
117+
dsn = params_impl.get_connect_string()
118+
119+
# see if connection is being acquired from a pool
120+
if pool is None:
121+
pool_impl = None
122+
elif not isinstance(pool, pool_module.ConnectionPool):
123+
message = "pool must be an instance of oracledb.ConnectionPool"
124+
raise TypeError(message)
125+
else:
126+
pool._verify_open()
127+
pool_impl = pool._impl
128+
129+
# create thin or thick implementation object
130+
if thin:
131+
if pool is not None:
132+
impl = pool_impl.acquire(params_impl)
133+
else:
134+
impl = thin_impl.ThinConnImpl(dsn, params_impl)
135+
impl.connect(params_impl)
132136
else:
133-
impl = thin_impl.ThinConnImpl(dsn, params_impl)
134-
impl.connect(params_impl)
135-
else:
136-
impl = thick_impl.ThickConnImpl(dsn, params_impl)
137-
impl.connect(params_impl, pool_impl)
138-
self._impl = impl
139-
self._version = None
140-
141-
# invoke callback, if applicable
142-
if impl.invoke_session_callback and pool is not None \
143-
and pool.session_callback is not None \
144-
and callable(pool.session_callback):
145-
pool.session_callback(self, params_impl.tag)
146-
impl.invoke_session_callback = False
137+
impl = thick_impl.ThickConnImpl(dsn, params_impl)
138+
impl.connect(params_impl, pool_impl)
139+
self._impl = impl
140+
self._version = None
141+
142+
# invoke callback, if applicable
143+
if impl.invoke_session_callback and pool is not None \
144+
and pool.session_callback is not None \
145+
and callable(pool.session_callback):
146+
pool.session_callback(self, params_impl.tag)
147+
impl.invoke_session_callback = False
147148

148149
def __del__(self):
149150
if self._impl is not None:

src/oracledb/driver_mode.py

Lines changed: 71 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -31,43 +31,83 @@
3131
# simultaneously.
3232
#------------------------------------------------------------------------------
3333

34-
from . import errors
34+
import threading
3535

36-
# this flag is used to indicate which mode is currently being used:
37-
# None: neither thick nor thin implementation has been used yet
38-
# False: thick implementation is being used
39-
# True: thin implementation is being used
40-
thin_mode = None
36+
from . import errors
4137

42-
def check_and_return_mode(requested_thin_mode=None):
38+
# The DriverModeHandler class is used to manage which mode the driver is using.
39+
#
40+
# The "thin_mode" flag contains the current state:
41+
# None: neither thick nor thin implementation has been used yet
42+
# False: thick implementation is being used
43+
# True: thin implementation is being used
44+
#
45+
# The "requested_thin_mode" flag is set to the mode that is being requested:
46+
# False: thick implementation is being initialized
47+
# True: thin implementation is being initialized
48+
class DriverModeManager:
4349
"""
44-
Internal function to return the current mode of python-oracledb.
50+
Manages the mode the driver is using. The "thin_mode" flag contains the
51+
current state:
52+
None: neither thick nor thin implementation has been used yet
53+
False: thick implementation is being used
54+
True: thin implementation is being used
55+
The "requested_thin_mode" is set to the mode that is being requested, but
56+
only while initialization is taking place (otherwise, it contains the value
57+
None):
58+
False: thick implementation is being initialized
59+
True: thin implementation is being initialized
60+
The condition is used to ensure that only one thread is performing
61+
initialization.
62+
"""
63+
def __init__(self):
64+
self.thin_mode = None
65+
self.requested_thin_mode = None
66+
self.condition = threading.Condition()
67+
68+
def __enter__(self):
69+
return self
4570

46-
If neither the thick nor the thin implementation have been used yet (the
47-
value of thin_mode is None), then:
71+
def __exit__(self, exc_type, exc_value, exc_tb):
72+
with self.condition:
73+
if exc_type is None and exc_value is None and exc_tb is None \
74+
and self.requested_thin_mode is not None:
75+
self.thin_mode = self.requested_thin_mode
76+
self.requested_thin_mode = None
77+
self.condition.notify()
4878

49-
- the mode is set to the requested mode, or
79+
@property
80+
def thin(self):
81+
if self.requested_thin_mode is not None:
82+
return self.requested_thin_mode
83+
return self.thin_mode
5084

51-
- the mode is set to thin, if no mode is requested.
85+
manager = DriverModeManager()
5286

53-
Otherwise, if requested_thin_mode is used and the mode requested
54-
does not match the current mode, an error is raised.
87+
def get_manager(requested_thin_mode=None):
88+
"""
89+
Returns the manager, but only after ensuring that no other threads are
90+
attempting to initialize the mode.
5591
5692
NOTE: the current implementation of the driver only requires
5793
requested_thin_mode to be set when initializing the thick mode; for this
5894
reason the error raised is specified about a thin mode connection already
5995
being created. If this assumption changes, a new error message will be
6096
required.
6197
"""
62-
global thin_mode
63-
if thin_mode is None:
64-
if requested_thin_mode is None:
65-
thin_mode = True
66-
else:
67-
thin_mode = requested_thin_mode
68-
elif requested_thin_mode is not None and requested_thin_mode != thin_mode:
69-
errors._raise_err(errors.ERR_THIN_CONNECTION_ALREADY_CREATED)
70-
return thin_mode
98+
with manager.condition:
99+
if manager.thin_mode is None:
100+
if manager.requested_thin_mode is not None:
101+
manager.condition.wait()
102+
if manager.thin_mode is None:
103+
if requested_thin_mode is None:
104+
manager.requested_thin_mode = True
105+
else:
106+
manager.requested_thin_mode = requested_thin_mode
107+
elif requested_thin_mode is not None \
108+
and requested_thin_mode != manager.thin_mode:
109+
errors._raise_err(errors.ERR_THIN_CONNECTION_ALREADY_CREATED)
110+
return manager
71111

72112

73113
def is_thin_mode() -> bool:
@@ -77,14 +117,14 @@ def is_thin_mode() -> bool:
77117
78118
Immediately after python-oracledb is imported, this function will return
79119
True indicating that python-oracledb defaults to Thin mode. If
80-
oracledb.init_oracle_client() is called, then a subsequent call to
81-
is_thin_mode() will return False indicating that Thick mode is enabled.
82-
Once the first standalone connection or connection pool is created, or a
83-
call to oracledb.init_oracle_client() is made, then python-oracledb's mode
84-
is fixed and the value returned by is_thin_mode() will never change for the
85-
lifetime of the process.
120+
oracledb.init_oracle_client() is called successfully, then a subsequent
121+
call to is_thin_mode() will return False indicating that Thick mode is
122+
enabled. Once the first standalone connection or connection pool is
123+
created succesfully, or a call to oracledb.init_oracle_client() is made
124+
successfully, then python-oracledb's mode is fixed and the value returned
125+
by is_thin_mode() will never change for the lifetime of the process.
86126
87127
"""
88-
if thin_mode is not None:
89-
return thin_mode
128+
if manager.thin_mode is not None:
129+
return manager.thin_mode
90130
return True

src/oracledb/impl/thick/utils.pyx

Lines changed: 26 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -452,31 +452,32 @@ def init_oracle_client(lib_dir=None, config_dir=None, error_url=None,
452452
if params_tuple != driver_context_params:
453453
errors._raise_err(errors.ERR_LIBRARY_ALREADY_INITIALIZED)
454454
return
455-
driver_mode.check_and_return_mode(requested_thin_mode=False)
456-
memset(&params, 0, sizeof(dpiContextCreateParams))
457-
encoding_bytes = constants.ENCODING.encode()
458-
params.defaultEncoding = encoding_bytes
459-
if config_dir is None:
460-
config_dir = defaults.config_dir
461-
if lib_dir is not None:
462-
lib_dir_bytes = lib_dir.encode()
463-
params.oracleClientLibDir = lib_dir_bytes
464-
if config_dir is not None:
465-
config_dir_bytes = config_dir.encode()
466-
params.oracleClientConfigDir = config_dir_bytes
467-
if driver_name is None:
468-
driver_name = f"{constants.DRIVER_NAME} thk : {VERSION}"
469-
driver_name_bytes = driver_name.encode()
470-
params.defaultDriverName = driver_name_bytes
471-
if error_url is not None:
472-
error_url_bytes = error_url.encode()
473-
else:
474-
error_url_bytes = constants.INSTALLATION_URL.encode()
475-
params.loadErrorUrl = error_url_bytes
476-
if dpiContext_createWithParams(DPI_MAJOR_VERSION, DPI_MINOR_VERSION,
477-
&params, &driver_context, &error_info) < 0:
478-
_raise_from_info(&error_info)
479-
driver_context_params = params_tuple
455+
with driver_mode.get_manager(requested_thin_mode=False) as mode_mgr:
456+
memset(&params, 0, sizeof(dpiContextCreateParams))
457+
encoding_bytes = constants.ENCODING.encode()
458+
params.defaultEncoding = encoding_bytes
459+
if config_dir is None:
460+
config_dir = defaults.config_dir
461+
if lib_dir is not None:
462+
lib_dir_bytes = lib_dir.encode()
463+
params.oracleClientLibDir = lib_dir_bytes
464+
if config_dir is not None:
465+
config_dir_bytes = config_dir.encode()
466+
params.oracleClientConfigDir = config_dir_bytes
467+
if driver_name is None:
468+
driver_name = f"{constants.DRIVER_NAME} thk : {VERSION}"
469+
driver_name_bytes = driver_name.encode()
470+
params.defaultDriverName = driver_name_bytes
471+
if error_url is not None:
472+
error_url_bytes = error_url.encode()
473+
else:
474+
error_url_bytes = constants.INSTALLATION_URL.encode()
475+
params.loadErrorUrl = error_url_bytes
476+
if dpiContext_createWithParams(DPI_MAJOR_VERSION, DPI_MINOR_VERSION,
477+
&params, &driver_context,
478+
&error_info) < 0:
479+
_raise_from_info(&error_info)
480+
driver_context_params = params_tuple
480481

481482

482483
def init_thick_impl(package):

src/oracledb/pool.py

Lines changed: 12 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -88,17 +88,18 @@ def __init__(self, dsn: str=None, *,
8888
params_impl.set(kwargs)
8989
self._connection_type = \
9090
params_impl.connectiontype or connection_module.Connection
91-
thin = driver_mode.check_and_return_mode()
92-
if dsn is not None:
93-
dsn = params_impl.parse_dsn(dsn, thin)
94-
if dsn is None:
95-
dsn = params_impl.get_connect_string()
96-
if thin:
97-
impl = thin_impl.ThinPoolImpl(dsn, params_impl)
98-
else:
99-
impl = thick_impl.ThickPoolImpl(dsn, params_impl)
100-
self._impl = impl
101-
self.session_callback = params_impl.session_callback
91+
with driver_mode.get_manager() as mode_mgr:
92+
thin = mode_mgr.thin
93+
if dsn is not None:
94+
dsn = params_impl.parse_dsn(dsn, thin)
95+
if dsn is None:
96+
dsn = params_impl.get_connect_string()
97+
if thin:
98+
impl = thin_impl.ThinPoolImpl(dsn, params_impl)
99+
else:
100+
impl = thick_impl.ThickPoolImpl(dsn, params_impl)
101+
self._impl = impl
102+
self.session_callback = params_impl.session_callback
102103

103104
def __del__(self):
104105
if self._impl is not None:

0 commit comments

Comments
 (0)