nodeocc 1.0.24__tar.gz → 1.0.25__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {nodeocc-1.0.24 → nodeocc-1.0.25}/PKG-INFO +2 -2
- {nodeocc-1.0.24 → nodeocc-1.0.25}/pyproject.toml +1 -1
- {nodeocc-1.0.24 → nodeocc-1.0.25}/src/controller/controller.py +103 -39
- {nodeocc-1.0.24 → nodeocc-1.0.25}/src/nodeocc.egg-info/PKG-INFO +2 -2
- {nodeocc-1.0.24 → nodeocc-1.0.25}/src/readers/slurmreader.py +41 -11
- {nodeocc-1.0.24 → nodeocc-1.0.25}/src/view/__init__.py +2 -0
- {nodeocc-1.0.24 → nodeocc-1.0.25}/src/view/curses_multiwindow.py +74 -24
- {nodeocc-1.0.24 → nodeocc-1.0.25}/README.md +0 -0
- {nodeocc-1.0.24 → nodeocc-1.0.25}/setup.cfg +0 -0
- {nodeocc-1.0.24 → nodeocc-1.0.25}/setup.py +0 -0
- {nodeocc-1.0.24 → nodeocc-1.0.25}/src/__init__.py +0 -0
- {nodeocc-1.0.24 → nodeocc-1.0.25}/src/model/infrastructure.py +0 -0
- {nodeocc-1.0.24 → nodeocc-1.0.25}/src/model/job.py +0 -0
- {nodeocc-1.0.24 → nodeocc-1.0.25}/src/nodeocc.egg-info/SOURCES.txt +0 -0
- {nodeocc-1.0.24 → nodeocc-1.0.25}/src/nodeocc.egg-info/dependency_links.txt +0 -0
- {nodeocc-1.0.24 → nodeocc-1.0.25}/src/nodeocc.egg-info/entry_points.txt +0 -0
- {nodeocc-1.0.24 → nodeocc-1.0.25}/src/nodeocc.egg-info/requires.txt +0 -0
- {nodeocc-1.0.24 → nodeocc-1.0.25}/src/nodeocc.egg-info/top_level.txt +0 -0
- {nodeocc-1.0.24 → nodeocc-1.0.25}/src/readers/compute_wait_time.py +0 -0
- {nodeocc-1.0.24 → nodeocc-1.0.25}/src/view/slurm_list.py +0 -0
- {nodeocc-1.0.24 → nodeocc-1.0.25}/src/view/slurm_viz.py +0 -0
- {nodeocc-1.0.24 → nodeocc-1.0.25}/src/view/styles.py +0 -0
- {nodeocc-1.0.24 → nodeocc-1.0.25}/src/view/utils.py +0 -0
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "nodeocc"
|
|
7
|
-
version = "1.0.
|
|
7
|
+
version = "1.0.25"
|
|
8
8
|
authors = [{ name = "Matteo Boschini" }]
|
|
9
9
|
maintainers = [{ name = "Lorenzo Bonicelli" }, { name = "Aniello Panariello" }]
|
|
10
10
|
description = "TUI application for viewing the status of GPU allocations on a Slurm cluster"
|
|
@@ -8,7 +8,7 @@ import numpy as np
|
|
|
8
8
|
|
|
9
9
|
conf_path = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
|
10
10
|
print(conf_path)
|
|
11
|
-
sys.path.
|
|
11
|
+
sys.path.insert(0, conf_path)
|
|
12
12
|
from curses import wrapper
|
|
13
13
|
from view.curses_multiwindow import main, Singleton, try_open_socket_as_slave
|
|
14
14
|
|
|
@@ -23,6 +23,7 @@ import time
|
|
|
23
23
|
import json
|
|
24
24
|
import argparse
|
|
25
25
|
import traceback
|
|
26
|
+
import socket
|
|
26
27
|
from model.infrastructure import Infrastructure
|
|
27
28
|
from model.job import Job
|
|
28
29
|
import asyncio
|
|
@@ -74,6 +75,7 @@ parser.add_argument('--debug', action='store_true', help='Enable logging')
|
|
|
74
75
|
parser.add_argument('--override', action='store_true', help='Enable logging')
|
|
75
76
|
parser.add_argument('--master_only', action='store_true', help='Disable all prints - only run in background')
|
|
76
77
|
parser.add_argument('--basepath', type=str, default='/tmp', help='Base path for nodeocc. Must be readable by all users')
|
|
78
|
+
parser.add_argument('--diag', action='store_true', help='Run one-shot diagnostics and exit')
|
|
77
79
|
args = parser.parse_args()
|
|
78
80
|
|
|
79
81
|
# check basepath is readable
|
|
@@ -177,49 +179,61 @@ async def get_data_slave(instance):
|
|
|
177
179
|
# listen for data from master
|
|
178
180
|
# instance.sock.settimeout(6.5)
|
|
179
181
|
data, _ = instance.sock.recvfrom(MAX_BUF)
|
|
180
|
-
decoded_data = data.decode('utf-8')
|
|
181
|
-
if BEGIN_DELIM in decoded_data:
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
182
|
+
decoded_data = data.decode('utf-8', errors='replace')
|
|
183
|
+
if BEGIN_DELIM not in decoded_data:
|
|
184
|
+
if EXTRA_MSG_BEGIN_DELIM in decoded_data:
|
|
185
|
+
instance.log("Received extra packet before main packet, discarding")
|
|
186
|
+
else:
|
|
187
|
+
instance.log("Received packet without known delimiters, discarding")
|
|
188
|
+
return None, None, None
|
|
189
|
+
|
|
190
|
+
n_msgs_remaining = int(decoded_data.split(BEGIN_DELIM)[0])
|
|
191
|
+
# msg_len += len(str(msg_len)) message len is deprecated and used for n_msgs_remaining
|
|
192
|
+
instance.timeme(f"received first message of {n_msgs_remaining} (total len {len(data)} bytes)")
|
|
193
|
+
|
|
194
|
+
inf, jobs, avg_wait_time, decoded_msg = decode_msg_slave(data)
|
|
195
|
+
instance.timeme(f"- receive")
|
|
196
|
+
orig_timestamp = float(decoded_msg['ts'])
|
|
197
|
+
|
|
198
|
+
total_msg_n = n_msgs_remaining
|
|
199
|
+
msg_store = {i: [] for i in range(total_msg_n)}
|
|
200
|
+
while n_msgs_remaining > 0:
|
|
201
|
+
data, _ = instance.sock.recvfrom(MAX_BUF)
|
|
202
|
+
decoded_extra = data.decode('utf-8', errors='replace')
|
|
203
|
+
if EXTRA_MSG_BEGIN_DELIM not in decoded_extra:
|
|
204
|
+
instance.log("Received non-extra packet while waiting for extra packets, skipping")
|
|
205
|
+
continue
|
|
206
|
+
|
|
207
|
+
back_msg_idx = int(decoded_extra.split(EXTRA_MSG_BEGIN_DELIM)[0])
|
|
208
|
+
msg_idx = total_msg_n - back_msg_idx - 1
|
|
209
|
+
if msg_idx < 0 or msg_idx >= total_msg_n:
|
|
210
|
+
instance.log(f"Invalid extra packet index: {msg_idx}, skipping")
|
|
211
|
+
continue
|
|
212
|
+
|
|
213
|
+
_, new_jobs, _, new_ts = decode_msg_slave(data, delim=EXTRA_MSG_BEGIN_DELIM)
|
|
214
|
+
|
|
215
|
+
# check if timestamp is the same
|
|
216
|
+
if float(new_ts['ts']) != orig_timestamp:
|
|
217
|
+
instance.timeme(f"Timestamp mismatch, skipping")
|
|
218
|
+
return inf, jobs, avg_wait_time # return what we have
|
|
219
|
+
|
|
220
|
+
msg_store[msg_idx] += new_jobs
|
|
221
|
+
|
|
222
|
+
n_msgs_remaining -= 1
|
|
223
|
+
instance.timeme(f"- extra receive, {n_msgs_remaining} remaining")
|
|
224
|
+
|
|
225
|
+
# sort and merge
|
|
226
|
+
for msg_idx in range(total_msg_n):
|
|
227
|
+
if len(msg_store[msg_idx]) == 0:
|
|
228
|
+
instance.log(f"Empty message {msg_idx}")
|
|
229
|
+
jobs += msg_store[msg_idx]
|
|
216
230
|
|
|
217
231
|
n_timeout = 0
|
|
218
232
|
|
|
219
|
-
except BlockingIOError
|
|
233
|
+
except BlockingIOError:
|
|
220
234
|
pass
|
|
221
235
|
|
|
222
|
-
except TimeoutError
|
|
236
|
+
except TimeoutError:
|
|
223
237
|
n_timeout += 1
|
|
224
238
|
instance.log(f"TIMEOUT {n_timeout}/{TIMEOUT_MAX}")
|
|
225
239
|
if n_timeout > TIMEOUT_MAX:
|
|
@@ -227,6 +241,10 @@ async def get_data_slave(instance):
|
|
|
227
241
|
|
|
228
242
|
time.sleep(random.randint(100, 10000) / 1000)
|
|
229
243
|
|
|
244
|
+
except Exception as e:
|
|
245
|
+
instance.err(f"Exception while receiving data from master: {e}")
|
|
246
|
+
instance.err(traceback.format_exc())
|
|
247
|
+
|
|
230
248
|
return inf, jobs, avg_wait_time
|
|
231
249
|
|
|
232
250
|
|
|
@@ -274,7 +292,53 @@ def display_main(stdscr):
|
|
|
274
292
|
return asyncio.run(main(stdscr))
|
|
275
293
|
|
|
276
294
|
|
|
295
|
+
def run_diagnostics(instance: Singleton):
|
|
296
|
+
hostname = socket.getfqdn().split('.')[0]
|
|
297
|
+
print(f"Diagnostics for nodeocc on host: {hostname}")
|
|
298
|
+
print(f"Base path: {instance.basepath}")
|
|
299
|
+
print(f"Role detected: {'master' if instance.is_master else 'slave'}")
|
|
300
|
+
print("")
|
|
301
|
+
|
|
302
|
+
try:
|
|
303
|
+
port_files = instance.get_port_files()
|
|
304
|
+
print(f"Visible port files: {len(port_files)}")
|
|
305
|
+
for pf in port_files:
|
|
306
|
+
try:
|
|
307
|
+
pid = Path(pf).read_text().strip()
|
|
308
|
+
except Exception:
|
|
309
|
+
pid = "?"
|
|
310
|
+
print(f" - {pf} (pid={pid})")
|
|
311
|
+
except Exception as e:
|
|
312
|
+
print(f"Could not list port files: {e}")
|
|
313
|
+
|
|
314
|
+
print("")
|
|
315
|
+
|
|
316
|
+
if instance.is_master:
|
|
317
|
+
print("Running one master update cycle...")
|
|
318
|
+
try:
|
|
319
|
+
inf, jobs = update_data_master(instance)
|
|
320
|
+
print(f"Master update OK. Nodes={len(inf.nodes)}, Jobs={len(jobs)}, Port={instance.port}")
|
|
321
|
+
except Exception as e:
|
|
322
|
+
print(f"Master update FAILED: {e}")
|
|
323
|
+
print(traceback.format_exc())
|
|
324
|
+
else:
|
|
325
|
+
print("Waiting for one packet from master...")
|
|
326
|
+
try:
|
|
327
|
+
inf, jobs, _ = asyncio.run(get_data_slave(instance))
|
|
328
|
+
if inf is None or jobs is None:
|
|
329
|
+
print("No valid payload received from master.")
|
|
330
|
+
else:
|
|
331
|
+
print(f"Slave receive OK. Jobs in snapshot={len(jobs)}")
|
|
332
|
+
except Exception as e:
|
|
333
|
+
print(f"Slave receive FAILED: {e}")
|
|
334
|
+
print(traceback.format_exc())
|
|
335
|
+
|
|
336
|
+
|
|
277
337
|
def _main():
|
|
338
|
+
if args.diag:
|
|
339
|
+
run_diagnostics(orig_instance)
|
|
340
|
+
return
|
|
341
|
+
|
|
278
342
|
if args.master_only:
|
|
279
343
|
assert orig_instance.is_master, "Instance is not `master`, run with `force_override` to override"
|
|
280
344
|
|
|
@@ -45,24 +45,54 @@ def _node_from_sinfo(x):
|
|
|
45
45
|
return n
|
|
46
46
|
|
|
47
47
|
|
|
48
|
-
def
|
|
49
|
-
|
|
50
|
-
|
|
48
|
+
def _parse_gpu_count(gres):
|
|
49
|
+
if not isinstance(gres, str):
|
|
50
|
+
return 0
|
|
51
|
+
gres = gres.strip()
|
|
52
|
+
if gres in ('(null)', ''):
|
|
53
|
+
return 0
|
|
54
|
+
|
|
55
|
+
n_gpu = 0
|
|
56
|
+
for token in gres.split(','):
|
|
57
|
+
token = token.strip()
|
|
58
|
+
if not token.lower().startswith('gpu'):
|
|
59
|
+
continue
|
|
60
|
+
last_field = token.split(':')[-1].split('(')[0]
|
|
61
|
+
match = re.search(r'\d+', last_field)
|
|
62
|
+
n_gpu += int(match.group(0)) if match else 1
|
|
63
|
+
return n_gpu
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def _parse_gpu_models(gres):
|
|
67
|
+
if not isinstance(gres, str):
|
|
51
68
|
return 'n/a'
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
69
|
+
gres = gres.strip()
|
|
70
|
+
if gres in ('(null)', ''):
|
|
71
|
+
return 'n/a'
|
|
72
|
+
|
|
73
|
+
models = []
|
|
74
|
+
for token in gres.split(','):
|
|
75
|
+
token = token.strip()
|
|
76
|
+
if not token.lower().startswith('gpu'):
|
|
77
|
+
continue
|
|
78
|
+
parts = token.split(':')
|
|
79
|
+
if len(parts) >= 3:
|
|
80
|
+
model = parts[1]
|
|
81
|
+
elif len(parts) == 2:
|
|
82
|
+
model = 'gpu'
|
|
55
83
|
else:
|
|
56
|
-
|
|
57
|
-
|
|
84
|
+
model = 'n/a'
|
|
85
|
+
if model not in models:
|
|
86
|
+
models.append(model)
|
|
87
|
+
|
|
88
|
+
return ','.join(models) if models else 'n/a'
|
|
58
89
|
|
|
59
90
|
|
|
60
91
|
def _read_nodes(reservations=None, pending_res=None, return_raw=False):
|
|
61
92
|
sinfo_df = pd.read_csv(StringIO(os.popen(r'sinfo -N -o "%N;%G;%t;%m;%c;%f"').read()), sep=';')
|
|
62
93
|
sinfo_df = _split_column(sinfo_df, 'NODELIST').drop_duplicates()
|
|
63
|
-
sinfo_df.loc[:, 'n_gpu'] = sinfo_df['GRES'].apply(
|
|
64
|
-
|
|
65
|
-
sinfo_df.loc[:, 'm_gpu'] = sinfo_df['GRES'].str.split(';').apply(_parse_gpu_models)
|
|
94
|
+
sinfo_df.loc[:, 'n_gpu'] = sinfo_df['GRES'].apply(_parse_gpu_count)
|
|
95
|
+
sinfo_df.loc[:, 'm_gpu'] = sinfo_df['GRES'].apply(_parse_gpu_models)
|
|
66
96
|
sinfo_df['reserved'] = 'no'
|
|
67
97
|
if reservations is not None:
|
|
68
98
|
sinfo_df.loc[sinfo_df['NODELIST'].isin(reservations.Nodes.unique()), 'reserved'] = 'yes'
|
|
@@ -3,6 +3,8 @@ from . import slurm_list, slurm_viz, styles
|
|
|
3
3
|
|
|
4
4
|
def update_views(stdscr, instance, filter):
|
|
5
5
|
if instance.jobs is None or instance.inf is None:
|
|
6
|
+
instance.rens = "No data received from master yet.\nTry running with --debug or --diag."
|
|
7
|
+
instance.nocc = "Waiting for infrastructure snapshot..."
|
|
6
8
|
return
|
|
7
9
|
|
|
8
10
|
instance.rens = slurm_list.view_list(
|
|
@@ -44,12 +44,49 @@ def is_file_readable_byall(file_path):
|
|
|
44
44
|
return os.stat(file_path).st_mode & stat.S_IROTH
|
|
45
45
|
|
|
46
46
|
|
|
47
|
+
def is_nodeocc_process(pid):
|
|
48
|
+
try:
|
|
49
|
+
cmdline = ' '.join(psutil.Process(int(pid)).cmdline())
|
|
50
|
+
return ('nodeocc' in cmdline) or ('controller.py' in cmdline)
|
|
51
|
+
except Exception:
|
|
52
|
+
return False
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def port_from_portfile(portfile_path):
|
|
56
|
+
try:
|
|
57
|
+
return int(Path(portfile_path).name.split('.')[0].split('master_')[1])
|
|
58
|
+
except Exception:
|
|
59
|
+
return None
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def process_has_udp_port(pid, port):
|
|
63
|
+
if port is None:
|
|
64
|
+
return False
|
|
65
|
+
try:
|
|
66
|
+
proc = psutil.Process(int(pid))
|
|
67
|
+
for conn in proc.connections(kind='udp'):
|
|
68
|
+
if conn.laddr and conn.laddr.port == int(port):
|
|
69
|
+
return True
|
|
70
|
+
except Exception:
|
|
71
|
+
return False
|
|
72
|
+
return False
|
|
73
|
+
|
|
74
|
+
|
|
47
75
|
def try_open_socket_as_slave(instance, force=False):
|
|
48
76
|
if len(instance.get_port_files()) == 0:
|
|
49
77
|
raise Exception("No master running")
|
|
50
78
|
|
|
51
|
-
|
|
52
|
-
|
|
79
|
+
active_ports = []
|
|
80
|
+
for f in instance.get_port_files():
|
|
81
|
+
pid = Path(f).read_text().strip()
|
|
82
|
+
port = port_from_portfile(f)
|
|
83
|
+
if pid and is_nodeocc_process(pid) and process_has_udp_port(pid, port):
|
|
84
|
+
active_ports.append(port)
|
|
85
|
+
|
|
86
|
+
if len(active_ports) == 0:
|
|
87
|
+
raise Exception("No active master process found from port files")
|
|
88
|
+
|
|
89
|
+
cur_port = active_ports[0]
|
|
53
90
|
|
|
54
91
|
if hasattr(instance, 'port'):
|
|
55
92
|
# check if port file has same name
|
|
@@ -96,15 +133,20 @@ class Singleton:
|
|
|
96
133
|
if not os.path.exists(bp):
|
|
97
134
|
return True
|
|
98
135
|
for f in os.listdir(bp):
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
pid = Path(
|
|
102
|
-
|
|
103
|
-
if
|
|
104
|
-
|
|
136
|
+
fp = os.path.join(bp, f)
|
|
137
|
+
if 'master_' in f and f.endswith('.portfile') and is_file_writable_byall(fp):
|
|
138
|
+
pid = Path(fp).read_text().strip()
|
|
139
|
+
port = port_from_portfile(fp)
|
|
140
|
+
if not pid:
|
|
141
|
+
os.remove(fp)
|
|
142
|
+
continue
|
|
143
|
+
if pid == str(os.getpid()):
|
|
144
|
+
os.remove(fp)
|
|
145
|
+
continue
|
|
146
|
+
if (not force) and is_nodeocc_process(pid) and process_has_udp_port(pid, port):
|
|
147
|
+
self.log(f"Process {pid} is still running, keeping existing master")
|
|
105
148
|
return False
|
|
106
|
-
|
|
107
|
-
os.system(f"rm {os.path.join(bp, f)}")
|
|
149
|
+
os.remove(fp)
|
|
108
150
|
return True
|
|
109
151
|
|
|
110
152
|
def __init__(self, args=None):
|
|
@@ -115,6 +157,7 @@ class Singleton:
|
|
|
115
157
|
Singleton.__instance = self
|
|
116
158
|
|
|
117
159
|
self.args = args
|
|
160
|
+
self.basepath = self.args.basepath
|
|
118
161
|
self.setup_logging()
|
|
119
162
|
|
|
120
163
|
self.try_open_counter = 0
|
|
@@ -141,11 +184,13 @@ class Singleton:
|
|
|
141
184
|
self.k = -1
|
|
142
185
|
|
|
143
186
|
# check if user group is 'student' or 'tesisti'
|
|
144
|
-
|
|
187
|
+
try:
|
|
188
|
+
user_group = grp.getgrgid(os.getgid()).gr_name
|
|
189
|
+
except KeyError:
|
|
190
|
+
user_group = ''
|
|
191
|
+
self.err(f"Could not resolve gid {os.getgid()} via grp.getgrgid")
|
|
145
192
|
self.cur_partition = 'stud' if user_group in ['studenti', 'tesisti'] else 'prod'
|
|
146
193
|
|
|
147
|
-
self.basepath = self.args.basepath
|
|
148
|
-
|
|
149
194
|
self.is_master = not self.check_existing_master_running()
|
|
150
195
|
|
|
151
196
|
if self.is_master or args.override:
|
|
@@ -158,9 +203,16 @@ class Singleton:
|
|
|
158
203
|
if len(portfiles) > 0:
|
|
159
204
|
for f in portfiles:
|
|
160
205
|
# check if process is still running
|
|
161
|
-
pid = Path(f).read_text()
|
|
162
|
-
if
|
|
163
|
-
|
|
206
|
+
pid = Path(f).read_text().strip()
|
|
207
|
+
if not pid:
|
|
208
|
+
continue
|
|
209
|
+
try:
|
|
210
|
+
pid_int = int(pid)
|
|
211
|
+
except ValueError:
|
|
212
|
+
continue
|
|
213
|
+
port = port_from_portfile(f)
|
|
214
|
+
if pid_int != os.getpid() and is_nodeocc_process(pid_int) and process_has_udp_port(pid_int, port):
|
|
215
|
+
return True
|
|
164
216
|
return False
|
|
165
217
|
|
|
166
218
|
def get_port_files(self):
|
|
@@ -182,11 +234,6 @@ class Singleton:
|
|
|
182
234
|
# get pid of current process
|
|
183
235
|
self.pid = os.getpid()
|
|
184
236
|
|
|
185
|
-
# clean up old port files
|
|
186
|
-
kill_ok = self.clean_port_files(force=force)
|
|
187
|
-
if not kill_ok:
|
|
188
|
-
return None
|
|
189
|
-
|
|
190
237
|
try:
|
|
191
238
|
# create file to store port with 666 permissions to file
|
|
192
239
|
# file is in folder basepath/nodename/master_{port}.portfile
|
|
@@ -211,10 +258,15 @@ class Singleton:
|
|
|
211
258
|
# check if file exists
|
|
212
259
|
if filepath.exists():
|
|
213
260
|
# delete file
|
|
214
|
-
os.
|
|
261
|
+
os.remove(filepath)
|
|
215
262
|
return None
|
|
216
263
|
|
|
217
264
|
def create_socket_as_master(self, force=False):
|
|
265
|
+
# clean up old port files before creating the socket to avoid inconsistent state
|
|
266
|
+
kill_ok = self.clean_port_files(force=force)
|
|
267
|
+
if not kill_ok:
|
|
268
|
+
return None
|
|
269
|
+
|
|
218
270
|
# create udp socket for broadcasting
|
|
219
271
|
self.sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, socket.IPPROTO_UDP)
|
|
220
272
|
|
|
@@ -248,8 +300,6 @@ class Singleton:
|
|
|
248
300
|
self.sock.settimeout(6.5)
|
|
249
301
|
self.log(f"Socket opened on port {self.port}")
|
|
250
302
|
|
|
251
|
-
self.update_port_file(force=force)
|
|
252
|
-
|
|
253
303
|
self.try_open_counter = 0
|
|
254
304
|
return self.port
|
|
255
305
|
except Exception as e:
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|