|
5 | 5 |
|
6 | 6 | import asyncio |
7 | 7 | import logging |
8 | | -import os |
9 | 8 | import random |
10 | 9 | import signal |
11 | 10 | import socket |
12 | | -import struct |
13 | 11 | import sys |
14 | 12 | import time |
15 | 13 | import types |
16 | 14 | from abc import ABC, abstractmethod |
17 | | -from enum import Enum, auto |
18 | 15 | from pathlib import Path |
19 | 16 | from typing import Dict, Iterator, List, Optional |
20 | 17 |
|
21 | 18 | import sysv_ipc |
22 | 19 |
|
23 | | -from homcc.client.host import ConnectionType, Host |
24 | 20 | from homcc.common.arguments import Arguments |
25 | 21 | from homcc.common.constants import TCP_BUFFER_SIZE |
26 | 22 | from homcc.common.errors import ( |
|
30 | 26 | RemoteHostsFailure, |
31 | 27 | SlotsExhaustedError, |
32 | 28 | ) |
| 29 | +from homcc.common.host import ConnectionType, Host |
33 | 30 | from homcc.common.messages import ArgumentMessage, DependencyReplyMessage, Message |
| 31 | +from homcc.common.statefile import StateFile |
34 | 32 |
|
35 | 33 | logger = logging.getLogger(__name__) |
36 | 34 |
|
@@ -220,157 +218,6 @@ def __enter__(self) -> LocalHostSemaphore: |
220 | 218 | self._timeout = self._timeout / 3 * 2 |
221 | 219 |
|
222 | 220 |
|
223 | | -class StateFile: |
224 | | - """ |
225 | | - Class to encapsulate and manage the current compilation status of a client via a state file. |
226 | | - This is heavily adapted from distcc so that we can easily use their monitoring tools. |
227 | | -
|
228 | | - The given distcc task state struct and how we replicate it is shown in the following: |
229 | | -
|
230 | | - struct dcc_task_state { |
231 | | - size_t struct_size; // DISTCC_TASK_STATE_STRUCT_SIZE |
232 | | - unsigned long magic; // DISTCC_STATE_MAGIC |
233 | | - unsigned long cpid; // pid |
234 | | - char file[128]; // source_base_filename |
235 | | - char host[128]; // hostname |
236 | | - int slot; // slot |
237 | | - enum dcc_phase curr_phase; // ClientPhase |
238 | | - struct dcc_task_state *next; // undefined for state file: 0 |
239 | | - }; |
240 | | -
|
241 | | - DISTCC_TASK_STATE_STRUCT_FORMAT provides an (un)packing format string for the above dcc_task_state struct. |
242 | | - """ |
243 | | - |
244 | | - class ClientPhase(int, Enum): |
245 | | - """Client compilation phases equivalent to dcc_phase.""" |
246 | | - |
247 | | - STARTUP = 0 |
248 | | - _BLOCKED = auto() # unused |
249 | | - CONNECT = auto() |
250 | | - CPP = auto() # Preprocessing |
251 | | - _SEND = auto() # unused |
252 | | - COMPILE = auto() |
253 | | - _RECEIVE = auto() # unused |
254 | | - _DONE = auto() # unused |
255 | | - |
256 | | - __slots__ = "pid", "source_base_filename", "hostname", "slot", "phase", "filepath" |
257 | | - |
258 | | - # size_t; unsigned long; unsigned long; char[128]; char[128]; int; enum (int); struct* (void*) |
259 | | - DISTCC_TASK_STATE_STRUCT_FORMAT: str = "NLL128s128siiP" |
260 | | - """Format string for the dcc_task_state struct to pack to and unpack from bytes for the state file.""" |
261 | | - |
262 | | - # constant dcc_task_state fields |
263 | | - DISTCC_TASK_STATE_STRUCT_SIZE: int = struct.calcsize(DISTCC_TASK_STATE_STRUCT_FORMAT) |
264 | | - """Total size of the dcc_task_state struct.""" |
265 | | - DISTCC_STATE_MAGIC: int = 0x44_49_48_00 # equal to: b"DIH\0" |
266 | | - """Magic number for the dcc_task_state struct.""" |
267 | | - DISTCC_NEXT_TASK_STATE: int = 0xFF_FF_FF_FF_FF_FF_FF_FF |
268 | | - """Undefined and unused pointer address for the next dcc_task_state struct*.""" |
269 | | - |
270 | | - HOMCC_STATE_DIR: Path = Path.home() / ".distcc" / "state" # TODO(s.pirsch): temporarily share state dir with distcc |
271 | | - """Path to the directory storing temporary homcc state files.""" |
272 | | - STATE_FILE_PREFIX: str = "binstate" |
273 | | - """Prefix for for state files.""" |
274 | | - |
275 | | - # none-constant dcc_task_state fields |
276 | | - pid: int |
277 | | - """Client Process ID.""" |
278 | | - source_base_filename: bytes |
279 | | - """Encoded base filename of the source file.""" |
280 | | - hostname: bytes |
281 | | - """Encoded host name.""" |
282 | | - slot: int |
283 | | - """Used host slot.""" |
284 | | - phase: ClientPhase |
285 | | - """Current compilation phase.""" |
286 | | - |
287 | | - # additional fields |
288 | | - filepath: Path # equivalent functionality as: dcc_get_state_filename |
289 | | - """Path to the state file.""" |
290 | | - |
291 | | - def __init__(self, arguments: Arguments, host: Host, state_dir: Path = HOMCC_STATE_DIR): |
292 | | - state_dir.mkdir(exist_ok=True, parents=True) |
293 | | - |
294 | | - # size_t struct_size: DISTCC_TASK_STATE_STRUCT_SIZE |
295 | | - # unsigned long magic: DISTCC_STATE_MAGIC |
296 | | - self.pid = os.getpid() # unsigned long cpid |
297 | | - |
298 | | - if source_files := arguments.source_files: |
299 | | - self.source_base_filename = Path(source_files[0]).name.encode() # char file[128] |
300 | | - elif output := arguments.output: |
301 | | - self.source_base_filename = output.encode() # take output target for linking instead |
302 | | - else: |
303 | | - logger.debug("No monitoring string deducible for %s.", arguments) |
304 | | - self.source_base_filename = "".encode() |
305 | | - |
306 | | - if len(self.source_base_filename) > 127: |
307 | | - logger.warning("Trimming too long Source Base Filename '%s'", self.source_base_filename.decode()) |
308 | | - self.source_base_filename = self.source_base_filename[:127] |
309 | | - |
310 | | - self.hostname = host.name.encode() # char host[128] |
311 | | - |
312 | | - if len(self.hostname) > 127: |
313 | | - logger.warning("Trimming too long Hostname '%s'", self.hostname.decode()) |
314 | | - self.hostname = self.hostname[:127] |
315 | | - |
316 | | - self.slot = 0 |
317 | | - |
318 | | - # state file path, e.g. ~/.homcc/state/binstate_pid |
319 | | - self.filepath = state_dir / f"{self.STATE_FILE_PREFIX}_{self.pid}" |
320 | | - |
321 | | - # enum dcc_phase curr_phase: unassigned |
322 | | - # struct dcc_task_state *next: DISTCC_NEXT_TASK_STATE |
323 | | - |
324 | | - def __bytes__(self) -> bytes: |
325 | | - # fmt: off |
326 | | - return struct.pack( |
327 | | - # struct format |
328 | | - self.DISTCC_TASK_STATE_STRUCT_FORMAT, |
329 | | - # struct fields |
330 | | - self.DISTCC_TASK_STATE_STRUCT_SIZE, # size_t struct_size |
331 | | - self.DISTCC_STATE_MAGIC, # unsigned long magic |
332 | | - self.pid, # unsigned long cpid |
333 | | - self.source_base_filename, # char file[128] |
334 | | - self.hostname, # char host[128] |
335 | | - self.slot, # int slot |
336 | | - self.phase, # enum dcc_phase curr_phase |
337 | | - self.DISTCC_NEXT_TASK_STATE, # struct dcc_task_state *next |
338 | | - ) |
339 | | - # fmt: on |
340 | | - |
341 | | - def __enter__(self) -> StateFile: |
342 | | - try: |
343 | | - self.filepath.touch(exist_ok=False) |
344 | | - except FileExistsError: |
345 | | - logger.debug("Could not create client state file '%s' as it already exists!", self.filepath.absolute()) |
346 | | - |
347 | | - self.set_startup() |
348 | | - |
349 | | - return self |
350 | | - |
351 | | - def __exit__(self, *_): |
352 | | - try: |
353 | | - self.filepath.unlink() |
354 | | - except FileNotFoundError: |
355 | | - logger.debug("File '%s' was already deleted!", self.filepath.absolute()) |
356 | | - |
357 | | - def _set_phase(self, phase: ClientPhase): |
358 | | - self.phase = phase |
359 | | - self.filepath.write_bytes(bytes(self)) |
360 | | - |
361 | | - def set_startup(self): |
362 | | - self._set_phase(self.ClientPhase.STARTUP) |
363 | | - |
364 | | - def set_connect(self): |
365 | | - self._set_phase(self.ClientPhase.CONNECT) |
366 | | - |
367 | | - def set_preprocessing(self): |
368 | | - self._set_phase(self.ClientPhase.CPP) |
369 | | - |
370 | | - def set_compile(self): |
371 | | - self._set_phase(self.ClientPhase.COMPILE) |
372 | | - |
373 | | - |
374 | 221 | class TCPClient: |
375 | 222 | """Wrapper class to exchange homcc protocol messages via TCP""" |
376 | 223 |
|
|
0 commit comments