Salome HOME
venv directory is configured in config_appli.xml file
[modules/kernel.git] / bin / PortManager.py
index 43038dbe6efa313919a7b94c72323139f2dfd3d7..a07bd40d49f1b72be4eb6b6a911875d47ab4828b 100644 (file)
@@ -1,6 +1,6 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
 #  -*- coding: iso-8859-1 -*-
-# Copyright (C) 2007-2014  CEA/DEN, EDF R&D, OPEN CASCADE
+# Copyright (C) 2007-2021  CEA/DEN, EDF R&D, OPEN CASCADE
 #
 # Copyright (C) 2003-2007  OPEN CASCADE, EADS/CCR, LIP6, CEA/DEN,
 # CEDRAT, EDF R&D, LEG, PRINCIPIA R&D, BUREAU VERITAS
 #
 import os
 import sys
+import psutil
 
-try:
-  import cPickle as pickle
-except:
-  import pickle
+from socket import AF_INET, SOCK_STREAM
+
+import pickle
+
+__PORT_MIN_NUMBER = 2810
+__PORT_MAX_NUMBER = 2910
 
 import logging
 def createLogger():
   logger = logging.getLogger(__name__)
-#  logger.setLevel(logging.DEBUG)
+  #logger.setLevel(logging.DEBUG)
+  logger.setLevel(logging.INFO)
   ch = logging.StreamHandler()
   ch.setLevel(logging.DEBUG)
-  formatter = logging.Formatter("%(levelname)s:%(threadName)s:%(message)s")
+  formatter = logging.Formatter("%(levelname)s:%(threadName)s:%(pathname)s[%(lineno)s]%(message)s")
   ch.setFormatter(formatter)
   logger.addHandler(ch)
   return logger
@@ -43,121 +47,123 @@ def createLogger():
 logger = createLogger()
 
 #------------------------------------
-# A file locker (Linux only)
-import fcntl
-class PortManagerLock:
-  def __init__(self, filename, readonly=False, blocking=True):
-    # This will create it if it does not exist already
-    logger.debug("Create lock on %s"%filename)
-    self.__readonly = readonly
-    self.__blocking = blocking
-    self.__filename = filename
-    flag = 'w'
-    if self.__readonly:
-      flag = 'r'
-    self.handle = open(self.__filename, 'a+')
-
-  def acquire(self):
-    mode = fcntl.LOCK_EX
-    if not self.__blocking: # Raise an IOError exception if file has already been locked
-      mode = mode | fcntl.LOCK_NB
-    fcntl.flock(self.handle, mode)
-    logger.debug("lock acquired %s"%self.__blocking)
-
-  def release(self):
-    fcntl.flock(self.handle, fcntl.LOCK_UN)
-    logger.debug("lock released")
-
-  def __del__(self):
-    logger.debug("Close lock file")
-    self.handle.close()
-    os.remove(self.__filename)
+# A file locker
+def __acquire_lock(lock):
+  logger.debug("ACQUIRE LOCK")
+  if sys.platform == "win32":
+    import msvcrt
+    # lock 1 byte: file is supposed to be zero-byte long
+    msvcrt.locking(lock.fileno(), msvcrt.LK_LOCK, 1)
+  else:
+    import fcntl
+    fcntl.flock(lock, fcntl.LOCK_EX)
+  logger.debug("LOCK ACQUIRED")
 #
+def __release_lock(lock):
+  logger.debug("RELEASE LOCK")
+  if sys.platform == "win32":
+    import msvcrt
+    msvcrt.locking(lock.fileno(), msvcrt.LK_UNLCK, 1)
+  else:
+    import fcntl
+    fcntl.flock(lock, fcntl.LOCK_UN)
+  logger.debug("LOCK RELEASED")
+#
+#------------------------------------
 
 def _getConfigurationFilename():
   omniorbUserPath = os.getenv("OMNIORB_USER_PATH")
 
   from salome_utils import generateFileName
   portmanager_config = generateFileName(omniorbUserPath,
-                                        prefix="omniORB",
+                                        prefix="salome",
                                         suffix="PortManager",
                                         extension="cfg",
                                         hidden=True)
-  lock_file = portmanager_config + "-lock"
+  import tempfile
+  temp = tempfile.NamedTemporaryFile()
+  lock_file = os.path.join(os.path.dirname(temp.name), ".salome", ".PortManager.lock")
+  try:
+    oldmask = os.umask(0)
+    os.makedirs(os.path.dirname(lock_file))
+  except IOError:
+    pass
+  finally:
+    os.umask(oldmask)
+  temp.close()
+
   return (portmanager_config, lock_file)
 #
 
-def __isPortUsed(port, busy_ports):
+def __isPortUsed(port, config):
+  busy_ports = []
+  for ports in config.values():
+    busy_ports += ports
   return (port in busy_ports) or __isNetworkConnectionActiveOnPort(port)
 #
 
 def __isNetworkConnectionActiveOnPort(port):
-  # :NOTE: Under windows:
-  #        netstat options -l and -t are unavailable
-  #        grep command is unavailable
-  from subprocess import Popen, PIPE
-  (stdout, stderr) = Popen(['netstat','-an'], stdout=PIPE).communicate()
-  import StringIO
-  buf = StringIO.StringIO(stdout)
-  ports = buf.readlines()
-  # search for TCP - LISTEN connections
-  import re
-  regObj = re.compile( ".*tcp.*:([0-9]+).*:.*listen", re.IGNORECASE );
-  for item in ports:
-    try:
-      p = int(regObj.match(item).group(1))
-      if p == port: return True
-    except:
-      pass
-#
+  # psutil realization
+  return port in [c.laddr.port for c in psutil.net_connections(kind='inet') if \
+      (c.family, c.type, c.status) == (AF_INET, SOCK_STREAM, "LISTEN")]
+  #
 
-def getPort(preferedPort=None):
+def getPort(preferredPort=None):
   logger.debug("GET PORT")
 
   config_file, lock_file = _getConfigurationFilename()
-  with open(lock_file, 'w') as lock:
+  oldmask = os.umask(0)
+  with open(lock_file, 'wb') as lock:
     # acquire lock
-    fcntl.flock(lock, fcntl.LOCK_EX)
+    __acquire_lock(lock)
 
     # read config
-    config = {'busy_ports':[]}
+    config = {}
     logger.debug("read configuration file")
     try:
-      with open(config_file, 'r') as f:
+      with open(config_file, 'rb') as f:
         config = pickle.load(f)
-    except IOError: # empty file
-      pass
+    except Exception:
+      logger.debug("Problem loading PortManager file: %s"%config_file)
+      # In this case config dictionary is reset
 
-    logger.debug("load busy_ports: %s"%str(config["busy_ports"]))
+    logger.debug("load config: %s"%str(config))
+    appli_path = os.getenv("ABSOLUTE_APPLI_PATH", "unknown")
+    try:
+        config[appli_path]
+    except KeyError:
+        config[appli_path] = []
 
     # append port
-    busy_ports = config["busy_ports"]
-    port = preferedPort
-    if not port or __isPortUsed(port, busy_ports):
-      port = 2810
-      while __isPortUsed(port, busy_ports):
-        if port == 2810+100:
+    port = preferredPort
+    if not port or __isPortUsed(port, config):
+      port = __PORT_MIN_NUMBER
+      while __isPortUsed(port, config):
+        if port == __PORT_MAX_NUMBER:
           msg  = "\n"
           msg += "Can't find a free port to launch omniNames\n"
           msg += "Try to kill the running servers and then launch SALOME again.\n"
-          raise RuntimeError, msg
+          raise RuntimeError(msg)
+        logger.debug("Port %s seems to be busy"%str(port))
         port = port + 1
     logger.debug("found free port: %s"%str(port))
-    config["busy_ports"].append(port)
+    config[appli_path].append(port)
 
     # write config
-    logger.debug("write busy_ports: %s"%str(config["busy_ports"]))
+    logger.debug("write config: %s"%str(config))
     try:
-      with open(config_file, 'w') as f:
-        pickle.dump(config, f)
+      with open(config_file, 'wb') as f:
+        pickle.dump(config, f, protocol=0)
     except IOError:
       pass
 
     # release lock
-    fcntl.flock(lock, fcntl.LOCK_UN)
+    __release_lock(lock)
+  #
 
-    logger.debug("get port: %s"%str(port))
-    return port
+  os.umask(oldmask)
+  logger.debug("get port: %s"%str(port))
+  return port
 #
 
 def releasePort(port):
@@ -165,62 +171,89 @@ def releasePort(port):
   logger.debug("RELEASE PORT (%s)"%port)
 
   config_file, lock_file = _getConfigurationFilename()
-  with open(lock_file, 'w') as lock:
+  oldmask = os.umask(0)
+  with open(lock_file, 'wb') as lock:
     # acquire lock
-    fcntl.flock(lock, fcntl.LOCK_EX)
+    __acquire_lock(lock)
 
     # read config
-    config = {'busy_ports':[]}
+    config = {}
     logger.debug("read configuration file")
     try:
-      with open(config_file, 'r') as f:
+      with open(config_file, 'rb') as f:
         config = pickle.load(f)
     except IOError: # empty file
       pass
 
-    logger.debug("load busy_ports: %s"%str(config["busy_ports"]))
+    logger.debug("load config: %s"%str(config))
+    appli_path = os.getenv("ABSOLUTE_APPLI_PATH", "unknown")
+    try:
+        config[appli_path]
+    except KeyError:
+        config[appli_path] = []
 
     # remove port from list
-    busy_ports = config["busy_ports"]
-
-    if port in busy_ports:
-      busy_ports.remove(port)
-      config["busy_ports"] = busy_ports
+    ports_info = config[appli_path]
+    config[appli_path] = [x for x in ports_info if x != port]
 
     # write config
-    logger.debug("write busy_ports: %s"%str(config["busy_ports"]))
+    logger.debug("write config: %s"%str(config))
     try:
-      with open(config_file, 'w') as f:
-        pickle.dump(config, f)
+      with open(config_file, 'wb') as f:
+        pickle.dump(config, f, protocol=0)
     except IOError:
       pass
 
     # release lock
-    fcntl.flock(lock, fcntl.LOCK_UN)
+    __release_lock(lock)
 
     logger.debug("released port port: %s"%str(port))
+
+  os.umask(oldmask)
 #
 
 def getBusyPorts():
   config_file, lock_file = _getConfigurationFilename()
-  with open(lock_file, 'w') as lock:
+  oldmask = os.umask(0)
+  with open(lock_file, 'wb') as lock:
     # acquire lock
-    fcntl.flock(lock, fcntl.LOCK_EX)
+    __acquire_lock(lock)
 
     # read config
-    config = {'busy_ports':[]}
+    config = {}
     logger.debug("read configuration file")
     try:
-      with open(config_file, 'r') as f:
+      with open(config_file, 'rb') as f:
         config = pickle.load(f)
     except IOError: # empty file
       pass
 
-    logger.debug("load busy_ports: %s"%str(config["busy_ports"]))
+    logger.debug("load config: %s"%str(config))
+    appli_path = os.getenv("ABSOLUTE_APPLI_PATH", "unknown")
+    try:
+        config[appli_path]
+    except KeyError:
+        config[appli_path] = []
+
+    # Scan all possible ports to determine which ones are owned by other applications
+    ports_info = { 'this': [], 'other': [] }
+    my_busy_ports = config[appli_path]
+    for port in range(__PORT_MIN_NUMBER, __PORT_MAX_NUMBER):
+      if __isPortUsed(port, config):
+        logger.debug("Port %s seems to be busy"%str(port))
+        if port in my_busy_ports:
+          ports_info["this"].append(port)
+        else:
+          ports_info["other"].append(port)
+
+    logger.debug("all busy_ports: %s"%str(ports_info))
+
+    sorted_ports = { 'this': sorted(ports_info['this']),
+                     'other': sorted(ports_info['other']) }
 
-    busy_ports = config["busy_ports"]
     # release lock
-    fcntl.flock(lock, fcntl.LOCK_UN)
+    __release_lock(lock)
 
-    return busy_ports
+  os.umask(oldmask)
+  return sorted_ports
 #