Răsfoiți Sursa

[rdbms] Fix multiple jdbc connectors port conflict (#4252)

fix(librdbms): improve Java Gateway management and configuration- Implement a singleton pattern for the Java Gateway to reuse a single instance- Add configuration option for Java binary path- Remove hard-coded Java path and use configured path instead.

Check: https://github.com/cloudera/hue/issues/4251
---------

Co-authored-by: yud8 <yud8@chinatelecom.cn>
dill 1 lună în urmă
părinte
comite
e7611e9e61

+ 3 - 0
desktop/conf.dist/hue.ini

@@ -288,6 +288,9 @@ http_500_debug_mode=false
 # Timeout in seconds for REST calls.
 ## rest_conn_timeout=120
 
+# Java binary path to add to PATH environment
+## java_path=/usr/java/default/bin
+
 # A json file containing a list of log redaction rules for cleaning sensitive data
 # from log files. It is defined as:
 #

+ 3 - 0
desktop/conf/pseudo-distributed.ini.tmpl

@@ -294,6 +294,9 @@
   # Timeout in seconds for REST calls.
   ## rest_conn_timeout=120
 
+  # Java binary path to add to PATH environment.
+  ## java_path=/usr/java/default/bin
+
   # A json file containing a list of log redaction rules for cleaning sensitive data
   # from log files. It is defined as:
   #

+ 7 - 0
desktop/core/src/desktop/conf.py

@@ -719,6 +719,13 @@ VCS = UnspecifiedConfigSection(
   )
 )
 
+JAVA_PATH = Config(
+  key="java_path",
+  help=_("Java binary path to add to PATH environment variable."),
+  default="",
+  type=str
+)
+
 
 def default_secure_cookie():
   """Enable secure cookies if HTTPS is enabled."""

+ 1 - 1
desktop/core/src/desktop/static/desktop/ext/topo/nul.topo.json

@@ -1 +1 @@
-{"type":"Topology","objects":{"nul":{"type":"GeometryCollection","geometries":[{"type":"Polygon","properties":{"name":null},"id":"-99","arcs":[[0]]}]}},"arcs":[[[0,9999],[9999,0],[0,-9999],[-9999,0],[0,9999]]],"transform":{"scale":[9.053905432543252e-10,9.053905432543252e-10],"translate":[-0.000004544999911,-0.000004496999935]}}
+{"type":"Topology","objects":{"nul":{"type":"GeometryCollection","geometries":[{"type":"Polygon","properties":{"name":null},"id":"-99","arcs":[[0]]}]}},"arcs":[[[0,9999],[9999,0],[0,-9999],[-9999,0],[0,9999]]],"transform":{"scale":[9.053905432543252e-10,9.053905432543252e-10],"translate":[-0.000004544999911,-0.000004496999935]}}

+ 60 - 11
desktop/libs/librdbms/src/librdbms/jdbc.py

@@ -15,12 +15,13 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-from builtins import range
-from builtins import object
 import logging
 import os
 import sys
+import threading
+from builtins import object, range
 
+from desktop.conf import JAVA_PATH
 from desktop.lib.i18n import force_unicode, smart_str
 from notebook.conf import DBPROXY_EXTRA_CLASSPATH
 from notebook.connectors.base import AuthenticationRequired
@@ -28,10 +29,55 @@ from notebook.connectors.base import AuthenticationRequired
 LOG = logging.getLogger()
 
 try:
-  from py4j.java_gateway import JavaGateway, JavaObject
-except:
+  from py4j.java_gateway import CallbackServerParameters, GatewayParameters, JavaGateway, JavaObject, launch_gateway
+except Exception:
   LOG.warning('Failed to import py4j')
 
+_GATEWAY_SINGLETON = None
+_GATEWAY_LOCK = threading.Lock()
+
+
+def _build_classpath():
+  classpath = os.environ.get('CLASSPATH', '')
+  extra = DBPROXY_EXTRA_CLASSPATH.get()
+  if extra:
+    classpath = f'{extra}:{classpath}' if classpath else extra
+  return classpath
+
+
+def _get_or_create_gateway():
+  global _GATEWAY_SINGLETON
+  if _GATEWAY_SINGLETON is not None:
+    return _GATEWAY_SINGLETON
+
+  with _GATEWAY_LOCK:
+    if _GATEWAY_SINGLETON is not None:
+      return _GATEWAY_SINGLETON
+
+    classpath = _build_classpath()
+
+    LOG.debug("[GatewaySingleton] starting Java Gateway...")
+    java_port = launch_gateway(
+      classpath=classpath,
+      port=0,
+      die_on_exit=True,
+      redirect_stdout=sys.stdout,
+      redirect_stderr=sys.stderr
+    )
+
+    _GATEWAY_SINGLETON = JavaGateway(
+      gateway_parameters=GatewayParameters(
+        port=java_port,
+        auto_convert=True,
+        eager_load=True
+      ),
+      callback_server_parameters=CallbackServerParameters(
+        port=0
+      )
+    )
+
+    return _GATEWAY_SINGLETON
+
 
 def query_and_fetch(db, statement, n=None):
   data = None
@@ -58,15 +104,18 @@ def query_and_fetch(db, statement, n=None):
 class Jdbc(object):
 
   def __init__(self, driver_name, url, username, password, impersonation_property=None, impersonation_user=None):
+    java_path = JAVA_PATH.get()
+    if java_path and java_path not in os.environ["PATH"]:
+      os.environ["PATH"] = os.environ["PATH"] + ':' + java_path
+
     if 'py4j' not in sys.modules:
       raise Exception('Required py4j module is not imported.')
 
-    os.environ["PATH"] = os.environ["PATH"] + ':/usr/java/default/bin' # TODO: more generic
-    classpath = os.environ.get('CLASSPATH', '')
-    if DBPROXY_EXTRA_CLASSPATH.get():
-      classpath = '%s:%s' % (DBPROXY_EXTRA_CLASSPATH.get(), classpath)
-
-    self.gateway = JavaGateway.launch_gateway(classpath=classpath)
+    try:
+      self.gateway = _get_or_create_gateway()
+    except Exception as e:
+      LOG.error(f"liibrdbms1-Java Gateway start error: {e}", exc_info=True)
+      raise
 
     self.jdbc_driver = driver_name
     self.db_url = url
@@ -137,7 +186,7 @@ class Cursor(object):
         cell = self.rs.getObject(c + 1)
 
         if isinstance(cell, JavaObject):
-          cell = str(cell) # DATETIME
+          cell = str(cell)  # DATETIME
         row.append(cell)
 
       res.append(row)