From 68093b12d01253b76fd87fcf7551f294c8c37f4d Mon Sep 17 00:00:00 2001 From: Michele Ceriotti Date: Sun, 7 Apr 2024 18:12:40 +0200 Subject: [PATCH 1/2] Added an option to override the default prefix for Unix sockets --- bin/i-pi | 5 ++++- drivers/f90/driver.f90 | 12 ++++++++---- drivers/f90/fsockets.f90 | 12 +++++++----- drivers/f90/sockets.c | 6 +++--- drivers/py/driver.py | 16 ++++++++++++++-- .../slurm_single_node/job_advanced.sh | 7 ++++--- ipi/engine/simulation.py | 14 +++++++++++++- ipi/inputs/simulation.py | 17 ++++++++++++++++- ipi/interfaces/sockets.py | 9 ++++++--- 9 files changed, 75 insertions(+), 23 deletions(-) diff --git a/bin/i-pi b/bin/i-pi index ee2aa291b..d26d8c92b 100755 --- a/bin/i-pi +++ b/bin/i-pi @@ -58,7 +58,7 @@ def main(fn_input, options): raise ImportError('Profiling requires the `yappi` package.') # construct simulation based on input file - simulation = Simulation.load_from_xml(fn_input, request_banner=True, custom_verbosity=options.verbosity) + simulation = Simulation.load_from_xml(fn_input, request_banner=True, custom_verbosity=options.verbosity, sockets_prefix=options.sockets_prefix) # run the simulation simulation.run() @@ -96,6 +96,9 @@ if __name__ == '__main__': choices=['quiet', 'low', 'medium', 'high', 'debug'], help='Define the verbosity level.') + parser.add_option('-S', '--sockets_prefix', dest='sockets_prefix', default=None, + help='Prefix for profiler files.') + options, args = parser.parse_args() # make sure that we have exactly one input file and it exists diff --git a/drivers/f90/driver.f90 b/drivers/f90/driver.f90 index 4ad8a4070..ae92a7cf4 100644 --- a/drivers/f90/driver.f90 +++ b/drivers/f90/driver.f90 @@ -37,7 +37,7 @@ PROGRAM DRIVER ! SOCKET VARIABLES INTEGER, PARAMETER :: MSGLEN=12 ! length of the headers of the driver/wrapper communication protocol INTEGER socket, inet, port ! socket ID & address of the server - CHARACTER(LEN=1024) :: host + CHARACTER(LEN=1024) :: host, sockets_prefix="/tmp/ipi_" ! COMMAND LINE PARSING CHARACTER(LEN=1024) :: cmdbuffer @@ -111,6 +111,8 @@ PROGRAM DRIVER ccmd = 3 ELSEIF (cmdbuffer == "-o") THEN ! reads the parameters ccmd = 4 + ELSEIF (cmdbuffer == "-S") THEN ! reads the socket prefix + ccmd = 5 ELSEIF (cmdbuffer == "-v") THEN ! flag for verbose standard output verbose = 1 ELSEIF (cmdbuffer == "-vv") THEN ! flag for verbose standard output @@ -205,6 +207,8 @@ PROGRAM DRIVER par_count = par_count + 1 ENDDO READ(cmdbuffer(commas(par_count)+1:),*) vpars(par_count) + ELSEIF (ccmd == 5) THEN + sockets_prefix = trim(cmdbuffer)//achar(0) ENDIF ccmd = 0 ENDIF @@ -470,7 +474,7 @@ PROGRAM DRIVER ENDIF ! Calls the interface to the POSIX sockets library to open a communication channel - CALL open_socket(socket, inet, port, host) + CALL open_socket(socket, inet, port, host, sockets_prefix) nat = -1 DO WHILE (.true.) ! Loops forever (or until the wrapper ends!) @@ -1048,8 +1052,8 @@ PROGRAM DRIVER CONTAINS SUBROUTINE helpmessage ! Help banner - WRITE(*,*) " SYNTAX: driver.x [-u] -a address -p port -m [dummy|gas|lj|sg|harm|harm3d|morse|morsedia|zundel|qtip4pf|pswater|lepsm1|lepsm2|qtip4p-efield|eckart|ch4hcbe|ljpolymer|MB|doublewell|doublewell_1D|water_dip_pol|harmonic_bath|meanfield_bath|qtip4pf-sr|qtip4pf-c-1|qtip4pf-c-2|qtip4pf-c-json|qtip4pf-c-1-delta|qtip4pf-c-2-delta|qtip4pf-c-json-delta]" - WRITE(*,*) " -o 'comma_separated_parameters' [-v] " + WRITE(*,*) " SYNTAX: driver.x [-u] -a address [-p port] -m [dummy|gas|lj|sg|harm|harm3d|morse|morsedia|zundel|qtip4pf|pswater|lepsm1|lepsm2|qtip4p-efield|eckart|ch4hcbe|ljpolymer|MB|doublewell|doublewell_1D|water_dip_pol|harmonic_bath|meanfield_bath|qtip4pf-sr|qtip4pf-c-1|qtip4pf-c-2|qtip4pf-c-json|qtip4pf-c-1-delta|qtip4pf-c-2-delta|qtip4pf-c-json-delta]" + WRITE(*,*) " -o 'comma_separated_parameters' [-S sockets_prefix] [-v] " WRITE(*,*) "" WRITE(*,*) " For LJ potential use -o sigma,epsilon,cutoff " WRITE(*,*) " For SG potential use -o cutoff " diff --git a/drivers/f90/fsockets.f90 b/drivers/f90/fsockets.f90 index ebf44720d..5e50b9cfb 100644 --- a/drivers/f90/fsockets.f90 +++ b/drivers/f90/fsockets.f90 @@ -56,10 +56,10 @@ SUBROUTINE c_sleep(seconds) bind(C, name="c_sleep") REAL(C_DOUBLE), value :: seconds END SUBROUTINE c_sleep - SUBROUTINE open_csocket(psockfd, inet, port, host) BIND(C, name="open_socket") + SUBROUTINE open_csocket(psockfd, inet, port, host, sockets_prefix) BIND(C, name="open_socket") USE ISO_C_BINDING INTEGER(KIND=C_INT) :: psockfd, inet, port - CHARACTER(KIND=C_CHAR), DIMENSION(*) :: host + CHARACTER(KIND=C_CHAR), DIMENSION(*) :: host, sockets_prefix END SUBROUTINE open_csocket @@ -90,15 +90,17 @@ SUBROUTINE f_sleep(sleep_seconds) CALL c_sleep(sleep_seconds) END SUBROUTINE - SUBROUTINE open_socket(psockfd, inet, port, host) + SUBROUTINE open_socket(psockfd, inet, port, host, sockets_prefix) IMPLICIT NONE INTEGER, INTENT(IN) :: inet, port INTEGER, INTENT(OUT) :: psockfd - CHARACTER(LEN=1024), INTENT(IN) :: host + CHARACTER(LEN=1024), INTENT(IN) :: host, sockets_prefix CHARACTER(LEN=1,KIND=C_CHAR) :: chost(1024) + CHARACTER(LEN=1,KIND=C_CHAR) :: csock(1024) CALL fstr2cstr(host, chost) - CALL open_csocket(psockfd, inet, port, host) + CALL fstr2cstr(sockets_prefix, csock) + CALL open_csocket(psockfd, inet, port, chost, csock) END SUBROUTINE SUBROUTINE fstr2cstr(fstr, cstr, plen) diff --git a/drivers/f90/sockets.c b/drivers/f90/sockets.c index dc2068c6a..8445117bc 100644 --- a/drivers/f90/sockets.c +++ b/drivers/f90/sockets.c @@ -51,7 +51,7 @@ Can be linked to a FORTRAN code that does not support sockets natively. #include #endif -void open_socket(int *psockfd, int* inet, int* port, const char* host) +void open_socket(int *psockfd, int* inet, int* port, const char* host, const char* sockets_prefix) /* Opens a socket. Note that fortran passes an extra argument for the string length, but this is @@ -107,8 +107,8 @@ ignored here for C compatibility. // fills up details of the socket addres memset(&serv_addr, 0, sizeof(serv_addr)); serv_addr.sun_family = AF_UNIX; - strcpy(serv_addr.sun_path, "/tmp/ipi_"); - strcpy(serv_addr.sun_path+9, host); + strcpy(serv_addr.sun_path, sockets_prefix); + strcpy(serv_addr.sun_path+strlen(sockets_prefix), host); // creates a unix socket // creates the socket diff --git a/drivers/py/driver.py b/drivers/py/driver.py index 0897def23..2c6378a66 100755 --- a/drivers/py/driver.py +++ b/drivers/py/driver.py @@ -59,14 +59,19 @@ def Message(mystr): def run_driver( - unix=False, address="", port=12345, driver=Dummy_driver(), f_verbose=False + unix=False, + address="", + port=12345, + driver=Dummy_driver(), + f_verbose=False, + sockets_prefix="/tmp/ipi_", ): """Minimal socket client for i-PI.""" # Opens a socket to i-PI if unix: sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) - sock.connect("/tmp/ipi_" + address) + sock.connect(sockets_prefix + address) else: sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # this reduces latency for the small messages passed by the i-PI protocol @@ -181,6 +186,13 @@ def run_driver( default="localhost", help="Host name (for INET sockets) or name of the UNIX domain socket to connect to.", ) + parser.add_argument( + "-S", + "--sockets_prefix", + type=str, + default="/tmp/ipi_", + help="Prefix used for the unix domain sockets. Ignored when using TCP/IP sockets.", + ) parser.add_argument( "-p", "--port", diff --git a/examples/hpc_scripts/slurm_single_node/job_advanced.sh b/examples/hpc_scripts/slurm_single_node/job_advanced.sh index a6f8a8af8..26ba6a8a6 100644 --- a/examples/hpc_scripts/slurm_single_node/job_advanced.sh +++ b/examples/hpc_scripts/slurm_single_node/job_advanced.sh @@ -30,12 +30,13 @@ IPI_PATH= ## Input file IPI_INPUT=input.xml +## Determines the address of the Unix-domain socket used in the input +IPI_ADDRESS=$(grep '
' $IPI_INPUT | sed 's/[^>]*>[[:space:]]*//; s/[[:space:]]*<.*//') + ## Driver command -IPI_DRIVER="i-pi-driver -a slurm-one-node -m zundel -u -v" +IPI_DRIVER="i-pi-driver -a $IPI_ADDRESS -m zundel -u -v" DRIVER_INPUT=driver.in # ignored for the example -## Determines the address of the Unix-domain socket used in the input -IPI_ADDRESS=$(grep '
' $IPI_INPUT | sed 's/[^>]*>[[:space:]]*//; s/[[:space:]]*<.*//') ## We create a UUID to make sure there are no name clashes with other processes, ## or with previous failed runs that left socket files behind diff --git a/ipi/engine/simulation.py b/ipi/engine/simulation.py index 96f2ffff0..ce896971f 100644 --- a/ipi/engine/simulation.py +++ b/ipi/engine/simulation.py @@ -59,7 +59,11 @@ class Simulation: @staticmethod def load_from_xml( - fn_input, custom_verbosity=None, request_banner=False, read_only=False + fn_input, + custom_verbosity=None, + sockets_prefix=None, + request_banner=False, + read_only=False, ): """Load an XML input file and return a `Simulation` object. @@ -69,6 +73,7 @@ def load_from_xml( specified by the input file. request_banner (bool): Whether to print the i-PI banner, if verbosity is higher than 'quiet'. + sockets_prefix (str): Use the specified prefix for all Unix domain sockets. """ # parse the file @@ -86,6 +91,11 @@ def load_from_xml( custom_verbosity = input_simulation.verbosity.fetch() input_simulation.verbosity.value = custom_verbosity + if sockets_prefix is None: + # Get from the input file + sockets_prefix = input_simulation.sockets_prefix.fetch() + input_simulation.sockets_prefix.value = sockets_prefix + # print banner if not suppressed and simulation verbose enough if request_banner and input_simulation.verbosity.value != "quiet": banner() @@ -122,6 +132,7 @@ def __init__( ttime=0, threads=False, safe_stride=1, + sockets_prefix="ipi_", ): """Initialises Simulation class. @@ -145,6 +156,7 @@ def __init__( self.mode = mode self.threading = threads self.safe_stride = safe_stride + self.sockets_prefix = sockets_prefix self.syslist = syslist for s in syslist: diff --git a/ipi/inputs/simulation.py b/ipi/inputs/simulation.py index 428db37cd..0974a2890 100644 --- a/ipi/inputs/simulation.py +++ b/ipi/inputs/simulation.py @@ -142,6 +142,14 @@ class InputSimulation(Input): "help": "A format for all printed floats.", }, ), + "sockets_prefix": ( + InputAttribute, + { + "dtype": str, + "default": "/tmp/ipi_", + "help": "A prefix prepended to all Unix-domain sockets.", + }, + ), } dynamic = { @@ -197,6 +205,7 @@ def store(self, simul): super(InputSimulation, self).store() + self.sockets_prefix.store(simul.sockets_prefix) self.output.store(simul.outtemplate) self.prng.store(simul.prng) self.step.store(simul.step) @@ -325,7 +334,12 @@ def fetch(self): or k == "ffcavphsocket" ): info(" # @simulation: Fetching" + k, verbosity.low) - fflist.append(v.fetch()) + new_ff = v.fetch() + if k == "ffsocket": + # overrides ffsocket prefix + print("OVERRIDIGN , ", new_ff.socket.sockets_prefix) + new_ff.socket.sockets_prefix = self.sockets_prefix.fetch() + fflist.append(new_ff) # this creates a simulation object which gathers all the little bits import ipi.engine.simulation as esimulation # import here as otherwise this is the mother of all circular imports... @@ -342,6 +356,7 @@ def fetch(self): ttime=self.total_time.fetch(), threads=self.threading.fetch(), safe_stride=self.safe_stride.fetch(), + sockets_prefix=self.sockets_prefix.fetch(), ) return rsim diff --git a/ipi/interfaces/sockets.py b/ipi/interfaces/sockets.py index 263a4544c..5f77cadb6 100644 --- a/ipi/interfaces/sockets.py +++ b/ipi/interfaces/sockets.py @@ -609,6 +609,7 @@ def __init__( match_mode="auto", exit_on_disconnect=False, max_workers=128, + sockets_prefix="/tmp/ipi_", ): """Initialises interface. @@ -632,6 +633,8 @@ def __init__( self.slots = slots self.mode = mode self.timeout = timeout + self.sockets_prefix = sockets_prefix + print(f"#### {self.sockets_prefix}") self.poll_iter = UPDATEFREQ # triggers pool_update at first poll self.prlist = [] # list of pending requests self.match_mode = match_mode # heuristics to match jobs and active clients @@ -650,14 +653,14 @@ def open(self): if self.mode == "unix": self.server = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) try: - self.server.bind("/tmp/ipi_" + self.address) + self.server.bind(self.sockets_prefix + self.address) info( "Created unix socket with address " + self.address, verbosity.medium ) except socket.error: raise RuntimeError( "Error opening unix socket. Check if a file " - + ("/tmp/ipi_" + self.address) + + (self.sockets_prefix + self.address) + " exists, and remove it if unused." ) @@ -720,7 +723,7 @@ def close(self): verbosity.low, ) if self.mode == "unix": - os.unlink("/tmp/ipi_" + self.address) + os.unlink(self.sockets_prefix + self.address) def poll(self): """Called in the main thread loop. From dc46535e00ac6569d732e9f451780a87b01b73f1 Mon Sep 17 00:00:00 2001 From: Michele Ceriotti Date: Mon, 8 Apr 2024 08:19:36 +0200 Subject: [PATCH 2/2] Added some docs --- docs/src/distributed.rst | 7 ++++++- ipi/inputs/simulation.py | 2 +- ipi/utils/inputvalue.py | 7 +++++-- 3 files changed, 12 insertions(+), 4 deletions(-) diff --git a/docs/src/distributed.rst b/docs/src/distributed.rst index 29fe61f44..ab7b5690d 100644 --- a/docs/src/distributed.rst +++ b/docs/src/distributed.rst @@ -141,7 +141,12 @@ client becomes a significant overhead for the calculation. UNIX-domain sockets create a special file in the local file system, that serves as a rendezvous point between server and clients, and are uniquely identified by the name of the file itself, that can be specified in the “address” -tag of in the xml input file and in the input of the client. +tag of in the xml input file and in the input of the client. By default +this file is created based on the address tag, with a `/tmp/ipi_` prefix. +This can be overridden setting the “sockets_prefix” attribute for the +:ref:`simulation` tag in the input file, or on the command-line using the +`-S` option. Note that several clients do not support changing the default +prefix. Unfortunately, UNIX sockets do not allow one to run i-PI and the clients on different computers, which limits greatly their utility when one diff --git a/ipi/inputs/simulation.py b/ipi/inputs/simulation.py index 0974a2890..6e133ddb4 100644 --- a/ipi/inputs/simulation.py +++ b/ipi/inputs/simulation.py @@ -147,7 +147,7 @@ class InputSimulation(Input): { "dtype": str, "default": "/tmp/ipi_", - "help": "A prefix prepended to all Unix-domain sockets.", + "help": "A prefix prepended to the `address` value to form the UNIX-domain socket location.", }, ), } diff --git a/ipi/utils/inputvalue.py b/ipi/utils/inputvalue.py index 1f4662a1a..7b0880933 100644 --- a/ipi/utils/inputvalue.py +++ b/ipi/utils/inputvalue.py @@ -829,12 +829,15 @@ def help_rst(self, name="", indent="", level=0, stop_level=None, standalone=True # For classes such as InputCell, self._default is not the value, # instead it is an object that is stored, putting the default value in # self.value. For this reason we print out self.value at this stage, - # and not self._default + # and not self._default. We also escape underscores that might appear + # in the default string value. rstr += ( "\n" + indent + "*default*: " - + self.pprint(self.value, indent=indent, latex=False) + + self.pprint(self.value, indent=indent, latex=False).replace( + "_", r"\_" + ) + "\n" )