Skip to content
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

fix --early-exit #437

Merged
merged 46 commits into from
Jan 28, 2025
Merged

fix --early-exit #437

merged 46 commits into from
Jan 28, 2025

Conversation

0xkarmacoma
Copy link
Collaborator

fixes #243

high-level notes:

  • the thread pool we were using during the assertion solving process did not allow us to interrupt work that was already started (thread_pool.shutdown(wait=False) was not what we needed it to be)
  • in order to be interruptible, we now run every solver instance as an external process (an actual subprocess.Popen object, wrapped for convenience in PopenFuture and scheduled with PopenExecutor)
  • when we call PopenExecutor.shutdown(wait=False), it does terminate/kill every process that has been submitted to that executor and is still running
  • an executor is scoped to a test function, so that a successful counterexample found for a path in a test function cancels the other solver instances for that test, but not for other tests that may be running concurrently
  • in order to support refinement, we still submit solve_end_to_end function calls to a thread pool, but that functions submits solve_low_level calls to the executor and blocks on the result. solve_low_level can be interrupted if the underlying process returns, times out, gets killed, etc -- this is the key architectural change
  • as a result, we no longer invoke the built-in z3 solve and every solver invocation is now an external solver invocation (hence --solver-command defaults to z3) and we need to parse the model values from the smtlib output. This means we no longer print Counterexample: see xyz.smt2

Additionally, to make the transition to this model easier I introduced a hierarchy of context objects:

  • ContractContext
  • FunctionContext
  • PathContext
  • each context has the associated args (HalmosConfig) with the relevant scoped overrides and intermediary values needed

@0xkarmacoma
Copy link
Collaborator Author

Things I checked:

  • works as expected without an explicit --solver-command (invokes an external z3 process)
  • works when passing an explicit --solver-command (caveat: bitwuzla and cvc require --produce-models, yices requires --smt2-model-format)
  • refinement works
  • timeouts work
  • we try to avoid leaving orphaned solvers behind, especially during timeouts
  • --early-exit really does print the first counterexample and cancels the other processes

Things I still need to check:

  • Windows support (I think --solver-command was never supported on Windows, but we now always require it)
  • ctrl-c behavior is likely wonky (it always was, but it should be fixable now, maybe as an additional improvement later)

@@ -0,0 +1,174 @@
import json
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

no changes here, just moving build-related functionality out of __main__.py and into its own module

@@ -0,0 +1,160 @@
import io
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

no changes here, just moving trace-related functionality out of __main__.py and into its own module

@@ -0,0 +1,217 @@
import concurrent.futures
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

new module that provides an executor/futures abstraction layer built on top of subprocess.Popen

return SolverOutput.from_result(stdout, stderr, returncode, path_ctx)


def solve_end_to_end(ctx: PathContext) -> None:
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

used to be gen_model_from_sexpr

@dataclass(frozen=True)
class PathContext:
# id of this path
path_id: int
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

roughly corresponds to the old iteration idx


refined_ctx = ctx.refine()

if refined_ctx.query.smtlib != query.smtlib:
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

related: #279

@0xkarmacoma
Copy link
Collaborator Author

TODO: fix the case where:

  • path exploration is running long
  • a counterexample is found early on
  • the solver executor is shut down, but path exploration continues
  • path exploration yield new potential paths to solve
  • we submit these new paths to the executor, but this causes an exception
ERROR    encountered exception during assertion solving: RuntimeError('Cannot submit to a shutdown executor.')                                                                                                                                                                                                               
ERROR    exception calling callback for <Future at 0x112aee4e0 state=finished raised RuntimeError>                                                                                                                                                                                                                           
         Traceback (most recent call last):                                                                                                                                                                                                                                                                                  
           File "/Users/karma/.pyenv/versions/3.12.1/lib/python3.12/concurrent/futures/_base.py", line 340, in _invoke_callbacks                                                                                                                                                                                             
             callback(self)                                                                                                                                                                                                                                                                                                  
           File "/Users/karma/projects/halmos/src/halmos/__main__.py", line 472, in solve_end_to_end_callback                                                                                                                                                                                                                
             solver_output = future.result()                                                                                                                                                                                                                                                                                 
                             ^^^^^^^^^^^^^^^                                                                                                                                                                                                                                                                                 
           File "/Users/karma/.pyenv/versions/3.12.1/lib/python3.12/concurrent/futures/_base.py", line 449, in result                                                                                                                                                                                                        
             return self.__get_result()                                                                                                                                                                                                                                                                                      
                    ^^^^^^^^^^^^^^^^^^^                                                                                                                                                                                                                                                                                      
           File "/Users/karma/.pyenv/versions/3.12.1/lib/python3.12/concurrent/futures/_base.py", line 401, in __get_result                                                                                                                                                                                                  
             raise self._exception                                                                                                                                                                                                                                                                                           
           File "/Users/karma/.pyenv/versions/3.12.1/lib/python3.12/concurrent/futures/thread.py", line 58, in run                                                                                                                                                                                                           
             result = self.fn(*self.args, **self.kwargs)                                                                                                                                                                                                                                                                     
                      ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^                                                                                                                                                                                                                                                                     
           File "/Users/karma/projects/halmos/src/halmos/solve.py", line 438, in solve_end_to_end                                                                                                                                                                                                                            
             solver_output = solve_low_level(ctx)                                                                                                                                                                                                                                                                            
                             ^^^^^^^^^^^^^^^^^^^^                                                                                                                                                                                                                                                                            
           File "/Users/karma/projects/halmos/src/halmos/solve.py", line 399, in solve_low_level                                                                                                                                                                                                                             
             path_ctx.solving_ctx.executor.submit(future)                                                                                                                                                                                                                                                                    
           File "/Users/karma/projects/halmos/src/halmos/processes.py", line 161, in submit                                                                                                                                                                                                                                  
             raise RuntimeError("Cannot submit to a shutdown executor.")                                                                                                                                                                                                                                                     
         RuntimeError: Cannot submit to a shutdown executor. 

We need some way to also interrupt path exploration when early exit is triggered.

@0xkarmacoma
Copy link
Collaborator Author

0xkarmacoma commented Jan 23, 2025

TODO: fix the case where:

* path exploration is running long

* a counterexample is found early on

* the solver executor is shut down, but path exploration continues

* path exploration yield new potential paths to solve

* we submit these new paths to the executor, but this causes an exception
ERROR    encountered exception during assertion solving: RuntimeError('Cannot submit to a shutdown executor.')                                                                                                                                                                                                               
ERROR    exception calling callback for <Future at 0x112aee4e0 state=finished raised RuntimeError>                                                                                                                                                                                                                           
         Traceback (most recent call last):                                                                                                                                                                                                                                                                                  
           File "/Users/karma/.pyenv/versions/3.12.1/lib/python3.12/concurrent/futures/_base.py", line 340, in _invoke_callbacks                                                                                                                                                                                             
             callback(self)                                                                                                                                                                                                                                                                                                  
           File "/Users/karma/projects/halmos/src/halmos/__main__.py", line 472, in solve_end_to_end_callback                                                                                                                                                                                                                
             solver_output = future.result()                                                                                                                                                                                                                                                                                 
                             ^^^^^^^^^^^^^^^                                                                                                                                                                                                                                                                                 
           File "/Users/karma/.pyenv/versions/3.12.1/lib/python3.12/concurrent/futures/_base.py", line 449, in result                                                                                                                                                                                                        
             return self.__get_result()                                                                                                                                                                                                                                                                                      
                    ^^^^^^^^^^^^^^^^^^^                                                                                                                                                                                                                                                                                      
           File "/Users/karma/.pyenv/versions/3.12.1/lib/python3.12/concurrent/futures/_base.py", line 401, in __get_result                                                                                                                                                                                                  
             raise self._exception                                                                                                                                                                                                                                                                                           
           File "/Users/karma/.pyenv/versions/3.12.1/lib/python3.12/concurrent/futures/thread.py", line 58, in run                                                                                                                                                                                                           
             result = self.fn(*self.args, **self.kwargs)                                                                                                                                                                                                                                                                     
                      ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^                                                                                                                                                                                                                                                                     
           File "/Users/karma/projects/halmos/src/halmos/solve.py", line 438, in solve_end_to_end                                                                                                                                                                                                                            
             solver_output = solve_low_level(ctx)                                                                                                                                                                                                                                                                            
                             ^^^^^^^^^^^^^^^^^^^^                                                                                                                                                                                                                                                                            
           File "/Users/karma/projects/halmos/src/halmos/solve.py", line 399, in solve_low_level                                                                                                                                                                                                                             
             path_ctx.solving_ctx.executor.submit(future)                                                                                                                                                                                                                                                                    
           File "/Users/karma/projects/halmos/src/halmos/processes.py", line 161, in submit                                                                                                                                                                                                                                  
             raise RuntimeError("Cannot submit to a shutdown executor.")                                                                                                                                                                                                                                                     
         RuntimeError: Cannot submit to a shutdown executor. 

We need some way to also interrupt path exploration when early exit is triggered.

✅ in 1fc8649

Copy link
Collaborator

@daejunpark daejunpark left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

overall looks good to me. thanks for this huge effort!

i focused on checking refactoring preserves existing behaviors, and left some comments.

i didn't review the new process module in detail.

render_trace(setup_ex.context)

else:
setup_exs_no_error.append(setup_ex)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this refactoring is incorrect. the smt query needs to be generated at this point. the solver object is shared across paths, and solver.to_smt2() will return a different query if it is called after a different path is explored.

alternatively, we could consider modifying path.to_smt2() to not rely on the shared solver. the idea is to create a temporary new solver with a separate z3 context, add all path constraints to the new solver, and generate a smt query from it. this indeeds already happens when --cache-solver is enabled. so this approach would improve the code maintainability avoiding this potential mistake in the future. but, a downside is the additional performance overhead of creating a new solver and translating all constraints to the new context. wdyt?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

good catch, I didn't realize. I agree that managing paths and context is a bit of a minefield, I believe I have restored it to its original meaning in 9a9d3cd and made a comment about the landmine. We can re-evaluate later if a different approach is needed 🙏

path_ctx = PathContext(
args=args,
path_id=path_id,
query=ex.path.to_smt2(args),
Copy link
Collaborator

@daejunpark daejunpark Jan 24, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

as mentioned above, calling path.to_smt2() here will generate an incorrect query unless it's the last path.

the ci failure might be related to this:
https://github.com/a16z/halmos/actions/runs/12958246417/job/36148232775#step:9:27

0xkarmacoma and others added 7 commits January 24, 2025 17:08
and fix off by one error in num_execs
10x faster morpho-data-structures testProveNextBucket

[PASS] testProveNextBucket(uint256) (paths: 100, time: 123.20s (paths: 123.20s, models: 0.00s), bounds: [])

[PASS] testProveNextBucket(uint256) (paths: 100, time: 12.11s (paths: 11.28s, models: 0.83s), bounds: [])
f"loop condition: {cond}\n"
f"calldata: {ex.calldata()}\n"
f"path condition:\n{ex.path}\n"
)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

oh wow. great catch. (curious why it wasn't caught earlier by my performance regression testing.)

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it has a disproportionate effect on the morpho test because it bumps against the loop limit and also has very complex path conditions, not much visible effect on other tests

Copy link
Collaborator

@daejunpark daejunpark left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

looks good to me!

it seems we're still having intermittent failures with the morpho tests. there may be still hidden issues with z3 objects related to threading or garbage collection. but i'm ok with merging this for now and addressing it separately, so we can merge this long running pr. i'll defer to you.

@0xkarmacoma 0xkarmacoma merged commit b0dead0 into main Jan 28, 2025
64 of 65 checks passed
@0xkarmacoma 0xkarmacoma deleted the fix/early-exit branch January 28, 2025 17:23
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

--early-exit doesn't work as expected
2 participants