-
-
Notifications
You must be signed in to change notification settings - Fork 2.9k
Add daemon mode -- highly experimental #4130
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
- Don't die if client closed commection. - Support `@file` to read arguments from a file. - Call build.build() directly.
orig_stat = os.stat | ||
|
||
|
||
def stat_proxy(path: str) -> os.stat_result: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hm, we probably don't need this function. (It was an experiment to count stat() calls, and while the numbers are useful, the implementation feels pretty wrong.)
@@ -0,0 +1,308 @@ | |||
"""Type checker test cases""" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This started out as a clone of testcheck.py, but there are many small changes.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd really like to be able to refactor the duplications in the test files in order to eventually clean up the test logic and improve the usability (#3973 being a first step). A comment about the nature of these differences may help with that.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks.
@@ -3020,7 +3020,6 @@ tmp/mod.py:7: error: Revealed type is 'builtins.bytes' | |||
# cmd: mypy -m a | |||
# cmd2: mypy -m b | |||
# flags: --follow-imports=silent | |||
# flags2: --follow-imports=silent |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This was just redundant, and its presence thwarted my logic for skipping test that vary flags across runs.
data_files=data_files, | ||
classifiers=classifiers, | ||
cmdclass={'build_py': CustomPythonBuild}, | ||
install_requires = ['typed-ast >= 1.1.0, < 1.2.0'], | ||
install_requires = ['typed-ast >= 1.1.0, < 1.2.0', | ||
'psutil >= 5.4.0, < 5.5.0', |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is used by dmypy only, to report memory usage.
@@ -0,0 +1,20 @@ | |||
#!/usr/bin/env python3 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Cloned from scripts/stubgen.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You might want to also add a entry_point in setup.py if you want something runnable from an installed mypy distribution.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah! Sorry, I missed that.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@elazarg I've added some comments. In addition you should just diff testcheck.py and testdmypy.py to see some details (I scrubbed a lot of stuff because incremental is always true and the step is always >= 1 in testdmypy.py).
|
||
@classmethod | ||
def cases(cls) -> List[DataDrivenTestCase]: | ||
if sys.platform == 'win32': |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is new (dmypy is not supported on Windows, and doesn't work there).
if cls.has_stable_flags(case) and cls.is_incremental(case)] | ||
return c | ||
|
||
def run_case(self, testcase: DataDrivenTestCase) -> None: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This method is specialized for incremental==True.
tc = parse_test_cases(os.path.join(test_data_prefix, f), | ||
None, test_temp_dir, True) | ||
c += [case for case in tc | ||
if cls.has_stable_flags(case) and cls.is_incremental(case)] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These filters were added.
raise ValueError( | ||
'Output file {} exists though test case only has {} runs'.format( | ||
file, num_steps)) | ||
self.server = None # type: Optional[dmypy.Server] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Reset the server here.
for step in range(1, num_steps + 1): | ||
self.run_case_once(testcase, step) | ||
|
||
@classmethod |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Next two methods are new.
if os.path.exists(dn): | ||
shutil.rmtree(dn) | ||
|
||
def run_case_once(self, testcase: DataDrivenTestCase, incremental_step: int) -> None: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No default for incremental_step, and assert it's >= 1 below.
|
||
# Parse options after moving files (in case mypy.ini is being moved). | ||
options = self.parse_options(original_program_text, testcase, incremental_step) | ||
if incremental_step == 1: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Initialize server here.
for module_name, program_path, program_text in module_data: | ||
# Always set to none so we're forced to reread the module in incremental mode | ||
sources.append(build.BuildSource(program_path, module_name, None)) | ||
response = self.server.check(sources, alt_lib_path=test_temp_dir) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Call server.check() instead of build.build().
update_testcase_output(testcase, a) | ||
assert_string_arrays_equal(output, a, msg.format(testcase.file, testcase.line)) | ||
|
||
manager = self.server.last_manager |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Get manager from server instead of from BuildResult.
all(flag not in flag_list for flag in ['--python-version', '-2', '--py2'])): | ||
options.python_version = testcase_pyversion(testcase.file, testcase.name) | ||
|
||
options.use_builtins_fixtures = True |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hardcoding these flags here (rather than way up there).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This looks like a good starting point for the daemon mode. Left a bunch of minor comments.
mypy/dmypy.py
Outdated
# Argument parser. Subparsers are tied to action functions by the | ||
# @action(subparse) decorator. | ||
|
||
parser = argparse.ArgumentParser(description="Client for mymy daemon mode", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Typo: mymy
mypy/dmypy.py
Outdated
sys.stderr.flush() | ||
pid = os.fork() | ||
if pid: | ||
npid, sts = os.waitpid(pid, 0) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Add comment saying that this happens in the original/current process.
mypy/dmypy.py
Outdated
# Misc utilities. | ||
|
||
def receive(sock: socket.socket) -> Any: | ||
"""Receive data from a socket until EOF.""" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Document that this reads JSON data.
if method is None: | ||
return {'error': "Unrecognized command '%s'" % command} | ||
else: | ||
return method(self, **data) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The contents of data
are not verified here, though elsewhere JSON is checked pretty carefully. At least add a comment about this here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is still pending, but this can be merged anyway.
mypy/build.py
Outdated
@@ -122,7 +123,9 @@ def is_source(self, file: MypyFile) -> bool: | |||
def build(sources: List[BuildSource], | |||
options: Options, | |||
alt_lib_path: Optional[str] = None, | |||
bin_dir: Optional[str] = None) -> BuildResult: | |||
bin_dir: Optional[str] = None, | |||
saved_cache: Optional['SavedCache'] = None, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Document saved_cache
. Are we making some assumptions about it? Does it get mutated?
mypy/dmypy.py
Outdated
|
||
import mypy.build | ||
import mypy.errors | ||
import mypy.main |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Eventually we could import most mypy modules lazily so that the client would run quicker in case the server is already running.
""" | ||
try: | ||
pid, sockname = get_status() | ||
except SystemExit as err: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd prefer having a separate exception class for daemon errors, and a top-level try/except that will translate it to SystemExit
.
mypy/dmypy.py
Outdated
def check(self, sources: List[mypy.build.BuildSource], | ||
alt_lib_path: Optional[str] = None) -> Dict[str, Any]: | ||
bound_gc_callback = lambda phase, info: self.gc_callback(phase, info) | ||
self.gc_start_time = None # type: Optional[float] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd recommend moving the stats handling code to separate methods to keep the logic of this method less cluttered.
I encountered something that looks like a performance problem. I did these steps:
|
The performance problem is apparently not new -- I can reproduce this with standard |
In that case I'll create a new issue to track it. |
Created #4135 to track the performance issue. |
This is causing typeshed's build to fail (e.g. python/typeshed#1680). Followup from #4130.
This is causing typeshed's build to fail (e.g. python/typeshed#1680). Followup from #4130.
@@ -339,6 +350,27 @@ def default_lib_path(data_dir: str, | |||
# silent mode or simply not found. | |||
|
|||
|
|||
def cache_meta_from_dict(meta: Dict[str, Any], data_json: str) -> CacheMeta: | |||
sentinel = None # type: Any # the values will be post-validated below |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This comment should be now "validated by the caller" or similar.
This is causing typeshed's build to fail (e.g. python/typeshed#1680). Followup from #4130.
Many things probably need to change still, but I'd like to get this in before the merge conflicts get out of hand (there are a fair number of small changes to build.py, and 1-2 to main.py).