source: src/mlx/singleton.py

python3
Last change on this file was 1019:d45b3d2eeb1d, checked in by István Váradi <ivaradi@…>, 4 years ago

Fix in the singleton model handler for Python 3 (re #347)

File size: 11.2 KB
Line 
1
2from .util import utf2unicode
3
4import os
5import time
6
7#----------------------------------------------------------------------------
8
9## @package mlx.singleton
10#
11# Allow a single instance of an application to run.
12#
13# This module implements the logic needed to detect that an instance of the
14# application is already running, and if so, notifying that instance.
15#
16# On Windows, a mutex is used to ensure that only a single instance is running,
17# since a mutex with a certain name can be created by one process only. If the
18# current process is the only instance, a named pipe is created and a thread is
19# started to listen for messages. Otherwise a message is sent to that pipe,
20# which results in calling a callback function.
21#
22# On Linux a Unix socket is created protected by a lock file, and the messages
23# from other instances are received via this socket.
24#
25# The main window is raised if another instance is started.
26
27#----------------------------------------------------------------------------
28
29if os.name=="nt":
30 import win32event
31 import win32file
32 import win32pipe
33 import win32api
34 import winerror
35
36 import threading
37
38 class _PipeServer(threading.Thread):
39 """A server that creates a named pipe, and waits for messages on
40 that."""
41
42 BUFFER_SIZE = 4096
43
44 def __init__(self, pipeName, raiseCallback):
45 """Construct the server thread."""
46 super(_PipeServer, self).__init__()
47
48 self._pipeName = pipeName
49 self._raiseCallback = raiseCallback
50 self.daemon = True
51
52 def run(self):
53 """Perform the operation of the thread."""
54 try:
55 while True:
56 handle = self._createPipe()
57
58 if handle is None:
59 break
60
61 print("singleton._PipeServer.run: created the pipe")
62 try:
63 if win32pipe.ConnectNamedPipe(handle)==0:
64 print("singleton._PipeServer.run: client connection received")
65 (code, message) = \
66 win32file.ReadFile(handle,
67 _PipeServer.BUFFER_SIZE,
68 None)
69
70 if code==0:
71 print("singleton._PipeServer.run: message received from client")
72 self._raiseCallback()
73 else:
74 print("singleton._PipeServer.run: failed to read from the pipe")
75 except Exception as e:
76 print("singleton._PipeServer.run: exception:", end=' ')
77 print(utf2unicode(str(e)))
78 finally:
79 win32pipe.DisconnectNamedPipe(handle)
80 win32file.CloseHandle(handle)
81 except Exception as e:
82 print("singleton._PipeServer.run: fatal exception:", end=' ')
83 print(utf2unicode(str(e)))
84
85 def _createPipe(self):
86 """Create the pipe."""
87 handle = win32pipe.CreateNamedPipe(self._pipeName,
88 win32pipe.PIPE_ACCESS_INBOUND,
89 win32pipe.PIPE_TYPE_BYTE |
90 win32pipe.PIPE_READMODE_BYTE |
91 win32pipe.PIPE_WAIT,
92 win32pipe.PIPE_UNLIMITED_INSTANCES,
93 _PipeServer.BUFFER_SIZE,
94 _PipeServer.BUFFER_SIZE,
95 1000,
96 None)
97 if handle==win32file.INVALID_HANDLE_VALUE:
98 print("singleton._PipeServer.run: could not create the handle")
99 return None
100 else:
101 return handle
102
103 class SingleInstance(object):
104 """Creating an instance of this object checks if only one instance of
105 the process runs."""
106 def __init__(self, baseName, raiseCallback):
107 """Construct the single instance object.
108
109 raiseCallback is a function that will be called, if another
110 instance of the program is started."""
111 self._name = baseName + "_" + win32api.GetUserName()
112 self._mutex = win32event.CreateMutex(None, False, self._name)
113 self._isSingle = win32api.GetLastError() != \
114 winerror.ERROR_ALREADY_EXISTS
115 if self._isSingle:
116 self._startPipeServer(raiseCallback)
117 else:
118 self._notifySingleton()
119
120 def close(self):
121 """Close the instance by closing the mutex."""
122 if self._mutex:
123 win32api.CloseHandle(self._mutex)
124 self._mutex = None
125
126 def _getPipeName(self):
127 """Get the name of the pipe to be used for communication."""
128 return r'\\.\pipe\\' + self._name
129
130 def _startPipeServer(self, raiseCallback):
131 """Start the pipe server"""
132 pipeServer = _PipeServer(self._getPipeName(),
133 raiseCallback)
134 pipeServer.start()
135
136 def _notifySingleton(self):
137 """Notify the already running instance of the program of our
138 presence."""
139 pipeName = self._getPipeName()
140 for i in range(0, 3):
141 try:
142 f = open(pipeName, "wb")
143 f.write("hello")
144 f.close()
145 return
146 except Exception as e:
147 print("SingleInstance._notifySingleton: failed:", end=' ')
148 print(utf2unicode(str(e)))
149 time.sleep(0.5)
150
151 def __bool__(self):
152 """Return a boolean representation of the object.
153
154 It is True, if this is the single instance of the program."""
155 return self._isSingle
156
157 def __del__(self):
158 """Destroy the object."""
159 self.close()
160
161#----------------------------------------------------------------------------
162
163else: # os.name=="nt"
164 import fcntl
165 import socket
166 import tempfile
167 import threading
168
169 class _SocketServer(threading.Thread):
170 """Server thread to handle the Unix socket through which we are
171 notified of other instances starting."""
172 def __init__(self, socketName, raiseCallback):
173 """Construct the server."""
174 super(_SocketServer, self).__init__()
175
176 self._socketName = socketName
177 self._raiseCallback = raiseCallback
178
179 self.daemon = True
180
181
182 def run(self):
183 """Perform the thread's operation."""
184 try:
185 try:
186 os.remove(self._socketName)
187 except:
188 pass
189
190 s = socket.socket(socket.AF_UNIX, socket.SOCK_DGRAM)
191 s.bind(self._socketName)
192
193 while True:
194 s.recv(64)
195 self._raiseCallback()
196 except Exception as e:
197 print("singleton._SocketServer.run: fatal exception:", end=' ')
198 print(utf2unicode(str(e)))
199
200 class SingleInstance(object):
201 """Creating an instance of this object checks if only one instance of
202 the process runs."""
203 def __init__(self, baseName, raiseCallback):
204 """Construct the single instance object.
205
206 raiseCallback is a function that will be called, if another
207 instance of the program is started."""
208 baseName = baseName + "_" + os.environ["LOGNAME"]
209
210 tempDir = tempfile.gettempdir()
211 self._lockName = os.path.join(tempDir, baseName + ".lock")
212 self._socketName = os.path.join(tempDir, baseName + ".sock")
213
214 self._lockFile = open(self._lockName, "w")
215
216 self._isSingle = False
217 try:
218 fcntl.lockf(self._lockFile, fcntl.LOCK_EX | fcntl.LOCK_NB)
219 self._isSingle = True
220 except Exception as e:
221 self._lockFile.close()
222 self._lockFile = None
223 pass
224
225 if self._isSingle:
226 self._startSocketServer(raiseCallback)
227 else:
228 self._notifySingleton()
229
230 def close(self):
231 """Close the instance by closing the mutex."""
232 if self._isSingle:
233 if self._lockFile:
234 self._lockFile.close()
235 self._lockFile = None
236 try:
237 os.remove(self._lockName)
238 except:
239 pass
240 try:
241 os.remove(self._socketName)
242 except:
243 pass
244
245
246 def _startSocketServer(self, raiseCallback):
247 """Start the pipe server"""
248 pipeServer = _SocketServer(self._socketName, raiseCallback)
249 pipeServer.start()
250
251 def _notifySingleton(self):
252 """Notify the already running instance of the program of our
253 presence."""
254 s = socket.socket(socket.AF_UNIX, socket.SOCK_DGRAM)
255 for i in range(0, 3):
256 try:
257 s.connect(self._socketName)
258 s.send(bytes("hello", "ascii"))
259 s.close()
260 return
261 except Exception as e:
262 print("singleton.SingleInstance._notifySingleton: failed:", end=' ')
263 print(utf2unicode(str(e)))
264 time.sleep(0.5)
265
266 def __bool__(self):
267 """Return a boolean representation of the object.
268
269 It is True, if this is the single instance of the program."""
270 return self._isSingle
271
272 def __del__(self):
273 """Destroy the object."""
274 self.close()
275
276#----------------------------------------------------------------------------
277#----------------------------------------------------------------------------
278
279# MAVA Logger X-specific stuff
280
281#----------------------------------------------------------------------------
282
283# The callback to use
284raiseCallback = None
285
286#----------------------------------------------------------------------------
287
288def raiseCallbackWrapper():
289 """The actual function to be used as the callback.
290
291 It checks if raiseCallback is None, and if not, it calls that."""
292 callback = raiseCallback
293 if callback is not None:
294 callback()
295
296#----------------------------------------------------------------------------
297#----------------------------------------------------------------------------
298
299if __name__=="__main__":
300 def raiseCallback():
301 print("Raise the window!")
302
303 instance = SingleInstance("mlx", raiseCallback)
304 if instance:
305 print("The first instance")
306 time.sleep(10)
307 else:
308 print("The program is already running.")
309
Note: See TracBrowser for help on using the repository browser.