Skip to content

Commit c43f6a3

Browse files
authored
Cpl 7607 read state files (#78)
1 parent 4a4cf49 commit c43f6a3

File tree

12 files changed

+386
-212
lines changed

12 files changed

+386
-212
lines changed

homcc/client/client.py

Lines changed: 2 additions & 155 deletions
Original file line numberDiff line numberDiff line change
@@ -5,22 +5,18 @@
55

66
import asyncio
77
import logging
8-
import os
98
import random
109
import signal
1110
import socket
12-
import struct
1311
import sys
1412
import time
1513
import types
1614
from abc import ABC, abstractmethod
17-
from enum import Enum, auto
1815
from pathlib import Path
1916
from typing import Dict, Iterator, List, Optional
2017

2118
import sysv_ipc
2219

23-
from homcc.client.host import ConnectionType, Host
2420
from homcc.common.arguments import Arguments
2521
from homcc.common.constants import TCP_BUFFER_SIZE
2622
from homcc.common.errors import (
@@ -30,7 +26,9 @@
3026
RemoteHostsFailure,
3127
SlotsExhaustedError,
3228
)
29+
from homcc.common.host import ConnectionType, Host
3330
from homcc.common.messages import ArgumentMessage, DependencyReplyMessage, Message
31+
from homcc.common.statefile import StateFile
3432

3533
logger = logging.getLogger(__name__)
3634

@@ -220,157 +218,6 @@ def __enter__(self) -> LocalHostSemaphore:
220218
self._timeout = self._timeout / 3 * 2
221219

222220

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-
374221
class TCPClient:
375222
"""Wrapper class to exchange homcc protocol messages via TCP"""
376223

homcc/client/compilation.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,9 @@
1212
LocalHostSemaphore,
1313
RemoteHostSelector,
1414
RemoteHostSemaphore,
15-
StateFile,
1615
TCPClient,
1716
)
1817
from homcc.client.config import ClientConfig
19-
from homcc.client.host import Host
2018
from homcc.common.arguments import Arguments, ArgumentsExecutionResult, Compiler
2119
from homcc.common.constants import ENCODING
2220
from homcc.common.errors import (
@@ -30,13 +28,15 @@
3028
UnexpectedMessageTypeError,
3129
)
3230
from homcc.common.hashing import hash_file_with_path
31+
from homcc.common.host import Host
3332
from homcc.common.messages import (
3433
CompilationResultMessage,
3534
ConnectionRefusedMessage,
3635
DependencyRequestMessage,
3736
File,
3837
Message,
3938
)
39+
from homcc.common.statefile import StateFile
4040

4141
logger = logging.getLogger(__name__)
4242

homcc/client/parsing.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,11 @@
1212
from homcc import client
1313
from homcc.client.compilation import scan_includes
1414
from homcc.client.config import ClientConfig, ClientEnvironmentVariables, parse_config
15-
from homcc.client.host import Host
1615
from homcc.common.arguments import Arguments, Compiler
1716
from homcc.common.compression import Compression
1817
from homcc.common.constants import ENCODING
1918
from homcc.common.errors import HostParsingError, NoHostsFoundError
19+
from homcc.common.host import Host
2020
from homcc.common.logging import (
2121
Formatter,
2222
FormatterConfig,
File renamed without changes.

0 commit comments

Comments
 (0)