Salome HOME
Compilation under Windows: add missing header
[modules/kernel.git] / bin / PortManager.py
1 #!/usr/bin/env python3
2 #  -*- coding: iso-8859-1 -*-
3 # Copyright (C) 2007-2021  CEA/DEN, EDF R&D, OPEN CASCADE
4 #
5 # Copyright (C) 2003-2007  OPEN CASCADE, EADS/CCR, LIP6, CEA/DEN,
6 # CEDRAT, EDF R&D, LEG, PRINCIPIA R&D, BUREAU VERITAS
7 #
8 # This library is free software; you can redistribute it and/or
9 # modify it under the terms of the GNU Lesser General Public
10 # License as published by the Free Software Foundation; either
11 # version 2.1 of the License, or (at your option) any later version.
12 #
13 # This library is distributed in the hope that it will be useful,
14 # but WITHOUT ANY WARRANTY; without even the implied warranty of
15 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
16 # Lesser General Public License for more details.
17 #
18 # You should have received a copy of the GNU Lesser General Public
19 # License along with this library; if not, write to the Free Software
20 # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
21 #
22 # See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com
23 #
24 import os
25 import sys
26 import psutil
27
28 from socket import AF_INET, SOCK_STREAM
29
30 try:
31   import cPickle as pickle #@UnusedImport
32 except:
33   import pickle #@Reimport
34
35 __PORT_MIN_NUMBER = 2810
36 __PORT_MAX_NUMBER = 2910
37
38 import logging
39 def createLogger():
40   logger = logging.getLogger(__name__)
41   #logger.setLevel(logging.DEBUG)
42   logger.setLevel(logging.INFO)
43   ch = logging.StreamHandler()
44   ch.setLevel(logging.DEBUG)
45   formatter = logging.Formatter("%(levelname)s:%(threadName)s:%(pathname)s[%(lineno)s]%(message)s")
46   ch.setFormatter(formatter)
47   logger.addHandler(ch)
48   return logger
49 #
50 logger = createLogger()
51
52 #------------------------------------
53 # A file locker
54 def __acquire_lock(lock):
55   logger.debug("ACQUIRE LOCK")
56   if sys.platform == "win32":
57     import msvcrt
58     # lock 1 byte: file is supposed to be zero-byte long
59     msvcrt.locking(lock.fileno(), msvcrt.LK_LOCK, 1)
60   else:
61     import fcntl
62     fcntl.flock(lock, fcntl.LOCK_EX)
63   logger.debug("LOCK ACQUIRED")
64 #
65 def __release_lock(lock):
66   logger.debug("RELEASE LOCK")
67   if sys.platform == "win32":
68     import msvcrt
69     msvcrt.locking(lock.fileno(), msvcrt.LK_UNLCK, 1)
70   else:
71     import fcntl
72     fcntl.flock(lock, fcntl.LOCK_UN)
73   logger.debug("LOCK RELEASED")
74 #
75 #------------------------------------
76
77 def _getConfigurationFilename():
78   omniorbUserPath = os.getenv("OMNIORB_USER_PATH")
79
80   from salome_utils import generateFileName
81   portmanager_config = generateFileName(omniorbUserPath,
82                                         prefix="salome",
83                                         suffix="PortManager",
84                                         extension="cfg",
85                                         hidden=True)
86   import tempfile
87   temp = tempfile.NamedTemporaryFile()
88   lock_file = os.path.join(os.path.dirname(temp.name), ".salome", ".PortManager.lock")
89   try:
90     oldmask = os.umask(0)
91     os.makedirs(os.path.dirname(lock_file))
92   except IOError:
93     pass
94   finally:
95     os.umask(oldmask)
96   temp.close()
97
98   return (portmanager_config, lock_file)
99 #
100
101 def __isPortUsed(port, config):
102   busy_ports = []
103   for ports in config.values():
104     busy_ports += ports
105   return (port in busy_ports) or __isNetworkConnectionActiveOnPort(port)
106 #
107
108 def __isNetworkConnectionActiveOnPort(port):
109   # psutil realization
110   return port in [c.laddr.port for c in psutil.net_connections(kind='inet') if \
111       (c.family, c.type, c.status) == (AF_INET, SOCK_STREAM, "LISTEN")]
112   #
113
114 def getPort(preferredPort=None):
115   logger.debug("GET PORT")
116
117   config_file, lock_file = _getConfigurationFilename()
118   oldmask = os.umask(0)
119   with open(lock_file, 'wb') as lock:
120     # acquire lock
121     __acquire_lock(lock)
122
123     # read config
124     config = {}
125     logger.debug("read configuration file")
126     try:
127       with open(config_file, 'rb') as f:
128         config = pickle.load(f)
129     except:
130       logger.debug("Problem loading PortManager file: %s"%config_file)
131       # In this case config dictionary is reset
132
133     logger.debug("load config: %s"%str(config))
134     appli_path = os.getenv("ABSOLUTE_APPLI_PATH", "unknown")
135     try:
136         config[appli_path]
137     except KeyError:
138         config[appli_path] = []
139
140     # append port
141     port = preferredPort
142     if not port or __isPortUsed(port, config):
143       port = __PORT_MIN_NUMBER
144       while __isPortUsed(port, config):
145         if port == __PORT_MAX_NUMBER:
146           msg  = "\n"
147           msg += "Can't find a free port to launch omniNames\n"
148           msg += "Try to kill the running servers and then launch SALOME again.\n"
149           raise RuntimeError(msg)
150         logger.debug("Port %s seems to be busy"%str(port))
151         port = port + 1
152     logger.debug("found free port: %s"%str(port))
153     config[appli_path].append(port)
154
155     # write config
156     logger.debug("write config: %s"%str(config))
157     try:
158       with open(config_file, 'wb') as f:
159         pickle.dump(config, f, protocol=0)
160     except IOError:
161       pass
162
163     # release lock
164     __release_lock(lock)
165   #
166
167   os.umask(oldmask)
168   logger.debug("get port: %s"%str(port))
169   return port
170 #
171
172 def releasePort(port):
173   port = int(port)
174   logger.debug("RELEASE PORT (%s)"%port)
175
176   config_file, lock_file = _getConfigurationFilename()
177   oldmask = os.umask(0)
178   with open(lock_file, 'wb') as lock:
179     # acquire lock
180     __acquire_lock(lock)
181
182     # read config
183     config = {}
184     logger.debug("read configuration file")
185     try:
186       with open(config_file, 'rb') as f:
187         config = pickle.load(f)
188     except IOError: # empty file
189       pass
190
191     logger.debug("load config: %s"%str(config))
192     appli_path = os.getenv("ABSOLUTE_APPLI_PATH", "unknown")
193     try:
194         config[appli_path]
195     except KeyError:
196         config[appli_path] = []
197
198     # remove port from list
199     ports_info = config[appli_path]
200     config[appli_path] = [x for x in ports_info if x != port]
201
202     # write config
203     logger.debug("write config: %s"%str(config))
204     try:
205       with open(config_file, 'wb') as f:
206         pickle.dump(config, f, protocol=0)
207     except IOError:
208       pass
209
210     # release lock
211     __release_lock(lock)
212
213     logger.debug("released port port: %s"%str(port))
214
215   os.umask(oldmask)
216 #
217
218 def getBusyPorts():
219   config_file, lock_file = _getConfigurationFilename()
220   oldmask = os.umask(0)
221   with open(lock_file, 'wb') as lock:
222     # acquire lock
223     __acquire_lock(lock)
224
225     # read config
226     config = {}
227     logger.debug("read configuration file")
228     try:
229       with open(config_file, 'rb') as f:
230         config = pickle.load(f)
231     except IOError: # empty file
232       pass
233
234     logger.debug("load config: %s"%str(config))
235     appli_path = os.getenv("ABSOLUTE_APPLI_PATH", "unknown")
236     try:
237         config[appli_path]
238     except KeyError:
239         config[appli_path] = []
240
241     # Scan all possible ports to determine which ones are owned by other applications
242     ports_info = { 'this': [], 'other': [] }
243     my_busy_ports = config[appli_path]
244     for port in range(__PORT_MIN_NUMBER, __PORT_MAX_NUMBER):
245       if __isPortUsed(port, config):
246         logger.debug("Port %s seems to be busy"%str(port))
247         if port in my_busy_ports:
248           ports_info["this"].append(port)
249         else:
250           ports_info["other"].append(port)
251
252     logger.debug("all busy_ports: %s"%str(ports_info))
253
254     sorted_ports = { 'this': sorted(ports_info['this']),
255                      'other': sorted(ports_info['other']) }
256
257     # release lock
258     __release_lock(lock)
259
260   os.umask(oldmask)
261   return sorted_ports
262 #