7
7
"""
8
8
9
9
import argparse
10
+ import base64
10
11
import json
11
12
import os
13
+ import pickle
12
14
import signal
13
- import socket
15
+ import subprocess
14
16
import sys
15
17
import time
16
18
17
- from typing import Any , Callable , Dict , List , Mapping , Optional , Tuple
19
+ from typing import Any , Callable , Dict , Mapping , Optional , Tuple
18
20
19
21
from mypy .dmypy_util import STATUS_FILE , receive
22
+ from mypy .ipc import IPCClient , IPCException
23
+ from mypy .dmypy_os import alive , kill
24
+
20
25
from mypy .version import __version__
21
26
22
27
# Argument parser. Subparsers are tied to action functions by the
@@ -92,7 +97,7 @@ def __init__(self, prog: str) -> None:
92
97
help = "Server shutdown timeout (in seconds)" )
93
98
p .add_argument ('flags' , metavar = 'FLAG' , nargs = '*' , type = str ,
94
99
help = "Regular mypy flags (precede with --)" )
95
-
100
+ p . add_argument ( '--options-data' , help = argparse . SUPPRESS )
96
101
help_parser = p = subparsers .add_parser ('help' )
97
102
98
103
del p
@@ -179,10 +184,9 @@ def restart_server(args: argparse.Namespace, allow_sources: bool = False) -> Non
179
184
def start_server (args : argparse .Namespace , allow_sources : bool = False ) -> None :
180
185
"""Start the server from command arguments and wait for it."""
181
186
# Lazy import so this import doesn't slow down other commands.
182
- from mypy .dmypy_server import daemonize , Server , process_start_options
183
- if daemonize (Server (process_start_options (args .flags , allow_sources ),
184
- timeout = args .timeout ).serve ,
185
- args .log_file ) != 0 :
187
+ from mypy .dmypy_server import daemonize , process_start_options
188
+ start_options = process_start_options (args .flags , allow_sources )
189
+ if daemonize (start_options , timeout = args .timeout , log_file = args .log_file ):
186
190
sys .exit (1 )
187
191
wait_for_server ()
188
192
@@ -201,7 +205,7 @@ def wait_for_server(timeout: float = 5.0) -> None:
201
205
time .sleep (0.1 )
202
206
continue
203
207
# If the file's content is bogus or the process is dead, fail.
204
- pid , sockname = check_status (data )
208
+ check_status (data )
205
209
print ("Daemon started" )
206
210
return
207
211
sys .exit ("Timed out waiting for daemon to start" )
@@ -224,7 +228,6 @@ def do_run(args: argparse.Namespace) -> None:
224
228
if not is_running ():
225
229
# Bad or missing status file or dead process; good to start.
226
230
start_server (args , allow_sources = True )
227
-
228
231
t0 = time .time ()
229
232
response = request ('run' , version = __version__ , args = args .flags )
230
233
# If the daemon signals that a restart is necessary, do it
@@ -273,9 +276,9 @@ def do_stop(args: argparse.Namespace) -> None:
273
276
@action (kill_parser )
274
277
def do_kill (args : argparse .Namespace ) -> None :
275
278
"""Kill daemon process with SIGKILL."""
276
- pid , sockname = get_status ()
279
+ pid , _ = get_status ()
277
280
try :
278
- os . kill (pid , signal . SIGKILL )
281
+ kill (pid )
279
282
except OSError as err :
280
283
sys .exit (str (err ))
281
284
else :
@@ -363,7 +366,20 @@ def do_daemon(args: argparse.Namespace) -> None:
363
366
"""Serve requests in the foreground."""
364
367
# Lazy import so this import doesn't slow down other commands.
365
368
from mypy .dmypy_server import Server , process_start_options
366
- Server (process_start_options (args .flags , allow_sources = False ), timeout = args .timeout ).serve ()
369
+ if args .options_data :
370
+ from mypy .options import Options
371
+ options_dict , timeout , log_file = pickle .loads (base64 .b64decode (args .options_data ))
372
+ options_obj = Options ()
373
+ options = options_obj .apply_changes (options_dict )
374
+ if log_file :
375
+ sys .stdout = sys .stderr = open (log_file , 'a' , buffering = 1 )
376
+ fd = sys .stdout .fileno ()
377
+ os .dup2 (fd , 2 )
378
+ os .dup2 (fd , 1 )
379
+ else :
380
+ options = process_start_options (args .flags , allow_sources = False )
381
+ timeout = args .timeout
382
+ Server (options , timeout = timeout ).serve ()
367
383
368
384
369
385
@action (help_parser )
@@ -375,7 +391,7 @@ def do_help(args: argparse.Namespace) -> None:
375
391
# Client-side infrastructure.
376
392
377
393
378
- def request (command : str , * , timeout : Optional [float ] = None ,
394
+ def request (command : str , * , timeout : Optional [int ] = None ,
379
395
** kwds : object ) -> Dict [str , Any ]:
380
396
"""Send a request to the daemon.
381
397
@@ -384,35 +400,30 @@ def request(command: str, *, timeout: Optional[float] = None,
384
400
Raise BadStatus if there is something wrong with the status file
385
401
or if the process whose pid is in the status file has died.
386
402
387
- Return {'error': <message>} if a socket operation or receive()
403
+ Return {'error': <message>} if an IPC operation or receive()
388
404
raised OSError. This covers cases such as connection refused or
389
405
closed prematurely as well as invalid JSON received.
390
406
"""
407
+ response = {} # type: Dict[str, str]
391
408
args = dict (kwds )
392
409
args .update (command = command )
393
410
bdata = json .dumps (args ).encode ('utf8' )
394
- pid , sockname = get_status ()
395
- sock = socket .socket (socket .AF_UNIX )
396
- if timeout is not None :
397
- sock .settimeout (timeout )
411
+ _ , name = get_status ()
398
412
try :
399
- sock .connect (sockname )
400
- sock .sendall (bdata )
401
- sock .shutdown (socket .SHUT_WR )
402
- response = receive (sock )
403
- except OSError as err :
413
+ with IPCClient (name , timeout ) as client :
414
+ client .write (bdata )
415
+ response = receive (client )
416
+ except (OSError , IPCException ) as err :
404
417
return {'error' : str (err )}
405
418
# TODO: Other errors, e.g. ValueError, UnicodeError
406
419
else :
407
420
return response
408
- finally :
409
- sock .close ()
410
421
411
422
412
423
def get_status () -> Tuple [int , str ]:
413
424
"""Read status file and check if the process is alive.
414
425
415
- Return (pid, sockname ) on success.
426
+ Return (pid, connection_name ) on success.
416
427
417
428
Raise BadStatus if something's wrong.
418
429
"""
@@ -423,7 +434,7 @@ def get_status() -> Tuple[int, str]:
423
434
def check_status (data : Dict [str , Any ]) -> Tuple [int , str ]:
424
435
"""Check if the process is alive.
425
436
426
- Return (pid, sockname ) on success.
437
+ Return (pid, connection_name ) on success.
427
438
428
439
Raise BadStatus if something's wrong.
429
440
"""
@@ -432,16 +443,14 @@ def check_status(data: Dict[str, Any]) -> Tuple[int, str]:
432
443
pid = data ['pid' ]
433
444
if not isinstance (pid , int ):
434
445
raise BadStatus ("pid field is not an int" )
435
- try :
436
- os .kill (pid , 0 )
437
- except OSError :
446
+ if not alive (pid ):
438
447
raise BadStatus ("Daemon has died" )
439
- if 'sockname ' not in data :
440
- raise BadStatus ("Invalid status file (no sockname field)" )
441
- sockname = data ['sockname ' ]
442
- if not isinstance (sockname , str ):
443
- raise BadStatus ("sockname field is not a string" )
444
- return pid , sockname
448
+ if 'connection_name ' not in data :
449
+ raise BadStatus ("Invalid status file (no connection_name field)" )
450
+ connection_name = data ['connection_name ' ]
451
+ if not isinstance (connection_name , str ):
452
+ raise BadStatus ("connection_name field is not a string" )
453
+ return pid , connection_name
445
454
446
455
447
456
def read_status () -> Dict [str , object ]:
0 commit comments