Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 10 additions & 8 deletions .github/workflows/tests.yaml
Original file line number Diff line number Diff line change
@@ -1,25 +1,27 @@
name: Tests
on:
on:
pull_request:
branches:
- main
types: [assigned, opened, synchronize, reopened, ready_for_review]
paths:
paths:
- hs
- .github/**
- jit_runtime/**
- parallel-orch/**
- scheduler/**
- executor/**
- preprocessor/**
- overlay-sandbox/**
- test/**
- scripts/**
push:
branches:
- main
paths:
paths:
- hs
- jit_runtime/**
- parallel-orch/**
- scheduler/**
- executor/**
- preprocessor/**
- overlay-sandbox/**
- test/**
Expand All @@ -31,12 +33,12 @@ jobs:
strategy:
fail-fast: false
matrix:
os:
os:
- ubuntu-24.04
runs-on: ${{ matrix.os }}
if: github.event.pull_request.draft == false
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v2
- name: Set up Python 3.12
uses: actions/setup-python@v2
with:
Expand All @@ -53,7 +55,7 @@ jobs:
python_pkgs/bin/pip install -r requirements.txt
python_pkgs/bin/pip install psutil
python_pkgs/bin/python -c "import shasta; import libdash; import libbash"
(cd parallel-orch; make)
(cd executor; make)
- name: Running Correctness Tests
run: |
export PASH_SPEC_TOP=$(pwd)
Expand Down
8 changes: 5 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,10 @@ The project's top-level directory contains the following:
- `deps`: Dependencies required by `hs`.
- `docs`: Documentation and architectural diagrams.
- `model-checking`: Tools and utilities for model checking.
- `parallel-orch`: Main orchestration components.
- `pash-spec.sh`: Entry script to initiate the `hs` process.
- `scheduler`: Scheduler daemon — manages speculative execution order and dependency tracking.
- `executor`: Executor — runs commands in sandboxes with tracing.
- `jit_runtime`: JIT runtime — shell scripts sourced during execution for state management.
- `preprocessor`: Preprocessor — transforms shell ASTs for speculative execution.
- `README.md`: This documentation file.
- `report`: Generated reports related to test runs and performance metrics.
- `requirements.txt`: List of Python dependencies.
Expand All @@ -42,7 +44,7 @@ This script will handle all the necessary installations, including dependencies,

### Running `hs`

The main entry script to initiate `hs` is `pash-spec.sh`. This script sets up the necessary environment and invokes the orchestrator in `parallel-orch/orch.py`. It's designed to accept a variety of arguments to customize its behavior, such as setting debug levels or specifying log files.
The main entry script to initiate `hs` is the `hs` script. This script sets up the necessary environment, launches the scheduler daemon, preprocesses the input script, and executes it with speculative execution. It accepts a variety of arguments to customize its behavior, such as setting debug levels or specifying log files.

Example of running the script:

Expand Down
92 changes: 44 additions & 48 deletions docs/component_pointers.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

## hS Overview
hS is a speculative execution system that can run shell programs in an out-of-order fashion
while maintaining equivalence to sequantial execution.
while maintaining equivalence to sequantial execution.
It does this by transforming and executing the program in a controlled, sandboxed manner, then
carefully reason about the dependencies to before choosing to commit, discard, or re-execute the commands it speculated

Expand Down Expand Up @@ -37,35 +37,32 @@ done
## Preprocessing

This step analyzes and transforms input shell script.
(Also this is the part where the most legacy code and technical debts are.)

Major callpath:
```
deps/pash/pa.sh
deps/pash/compiler/pash.py:main
preprocess_and_execute_asts
deps/pash/compiler/preprocessor/preprocessor.py:preprocess
deps/pash/compiler/shell_ast/ast_to_ast.py:replace_ast_region
deps/pash/compiler/speculative/util_spec.py:serialize_partial_order
execute_script
hs (entry point)
preprocessor/preprocessor.py:preprocess
preprocessor/ast_transform.py:replace_ast_regions
preprocessor/transformation.py:serialize_partial_order
execute_script
```

`breakpoint()` at
- end of `deps/pash/compiler/preprocessor/preprocessor.py:preprocess_asts` to see "partial_order" files
- end of `preprocessor/preprocessor.py:preprocess_asts` to see "partial_order" files
- `preprocess_and_execute_asts` to see preprocessed script files (a.k.a. program skeleton)

Alternatively turn on `-d 2` and dig through logs

## Program Skeleton

Looking at the program skeleton we can see
- Control flow: the control flow structure is kept and
- Control flow: the control flow structure is kept and
- HS_LOOP_LIST: the implicit runtime control flow hint

Inside `pash_runtime.sh`, the major call path is:
Inside the JIT runtime, the major call path is:
```
deps/pash/compiler/pash_runtime.sh
deps/pash/compiler/orchestrator_runtime/speculative/speculative_runtime.sh
jit_runtime/jit.sh
(communicates with scheduler via unix socket)
```

We will come back to this when we look at command execution
Expand All @@ -78,13 +75,12 @@ We will come back to this when we look at command execution
### Core Data Structures
Major callpath:
```
deps/pash/compiler/orchestrator_runtime/speculative/pash_spec_init_setup.sh
scheduler_server.py:main
Scheduler.run
scheduler/scheduler_server.py:main
Scheduler.run
```
parallel_orch/node.py:`HSProg` --- backend's representation of the shell program
scheduler/node.py:`HSProg` --- backend's representation of the shell program
`HSBasicBlock` --- backend's representation of basic block
parallel_orch/partial_program_order.py:`PartialProgramOrder` --- Execution state of the shell program
scheduler/partial_program_order.py:`PartialProgramOrder` --- Execution state of the shell program
```Python
class CFGEdgeType(Enum):
IF_TAKEN = auto()
Expand Down Expand Up @@ -121,7 +117,7 @@ All of these information are directly parsed from the "partial order" file

## Command Execution and Sandboxing
### Core Data Structure
parallel_orch/node.py:
scheduler/node.py:
```Python
class NodeState(Enum):
INIT = auto()
Expand All @@ -138,41 +134,41 @@ class ConcreteNode:
cnid: ConcreteNodeId
abstract_node: Node
state: NodeState
# exists for EXEC or SPEC_E or subsequent states, erased for READY
# exists for EXEC or SPEC_E or subsequent states, erased for READY
exec_id: int
# Nodes to check for fs dependencies before this node can be committed
# for this particular execution of the main sandbox.
# No need to do the same for the background sandbox since it will always get committed.
# Nodes to check for fs dependencies before this node can be committed
# for this particular execution of the main sandbox.
# No need to do the same for the background sandbox since it will always get committed.
to_be_resolved_snapshot: "set[NodeId]"
# Read and write sets for this node
# Read and write sets for this node
rwset: RWSet
# The wait trace file for this node
# The wait trace file for this node
wait_env_file: str
# This can only be set while in the frontier and the background node execution is enabled
# TODO: For now ignore this. Maybe there is a better way to do this.
# background_sandbox: Sandbox
# This can only be set while in the frontier and the background node execution is enabled
# TODO: For now ignore this. Maybe there is a better way to do this.
# background_sandbox: Sandbox

# Exists when the node is in COMMITED or SPEC_F
# Exists when the node is in COMMITED or SPEC_F
exec_result: ExecResult

# Updated when the node is loop changing and the node is transitioning
# into COMMITTED or SPEC_F
# Updated when the node is loop changing and the node is transitioning
# into COMMITTED or SPEC_F
loop_list_context: HSLoopListContext

# read-only, the value of initial loop_list_context
# used when reset_to_ready
# read-only, the value of initial loop_list_context
# used when reset_to_ready
init_loop_list_context: HSLoopListContext

spec_pre_env: str

# Exists when node is in READY
# Exists when node is in READY
assignments: "list[NodeId]"

# Exists when node is in EXE or SPEC_EXE, it acts as a cache for
# the trace file content
# Exists when node is in EXE or SPEC_EXE, it acts as a cache for
# the trace file content
trace_lines: list
# Exists when node is in EXE or SPEC_EXE, it it an opened file
# or none when such file doesn't exist
# Exists when node is in EXE or SPEC_EXE, it it an opened file
# or none when such file doesn't exist
trace_fd=None
trace_ctx=None
```
Expand All @@ -181,29 +177,29 @@ Major callpath:
```
Node.start_executing
Node.start_command
parallel_orch/executor.py:run_trace_sandboxed
parallel-orch/run_command.sh
executor/executor.py:run_trace_sandboxed
executor/run_command.sh
fd_util -> try -> strace
```
The `env_file` is explicitly passed around. The environment capturing and restoring happens at `deps/pash/compiler/orchestrator_runtime/pash_declare_vars.sh` and `deps/pash/compiler/orchestrator_runtime/pash_source_declare_vars.sh`
The `env_file` is explicitly passed around. The environment capturing and restoring happens at `jit_runtime/pash_declare_vars.sh` and `jit_runtime/pash_source_declare_vars.sh`

`breakpoint()` at `parallel_orc/partial_program_order.py:handle_complete`, see the completed files
`breakpoint()` at `scheduler/partial_program_order.py:handle_complete`, see the completed files


## Backend: Speculation
### Core data structure
parallel-orch/partial_program_order.py:`PartialProgramOrder`
scheduler/partial_program_order.py:`PartialProgramOrder`
```Python
class PartialProgramOrder:
def __init__(self, abstract_nodes: "dict[NodeId, Node]", edges: "dict[NodeId, list[NodeId]]",
hs_prog: HSProg):
self.hsprog = hs_prog
self.concrete_nodes: dict[ConcreteNodeId, ConcreteNode] = {}
self.frontier = set()
# self.run_after = {}
# Nodes that we have received "wait" for
# self.run_after = {}
# Nodes that we have received "wait" for
self.canon_exec_order: list[ConcreteNodeId] = list()
# Nodes that we think should happen, and haven't received "wait" for
# Nodes that we think should happen, and haven't received "wait" for
self.spec_exec_order: list[ConcreteNodeId] = list()
self.to_be_resolved: dict[ConcreteNodeId, list[ConcreteNodeId]] = {}
self.temp_new_env = None
Expand All @@ -212,7 +208,7 @@ class PartialProgramOrder:

Major callpath:
```
parallel-orch/scheduler_server.py:Scheduler.run
scheduler/scheduler_server.py:Scheduler.run
Scheduler.process_next_cmd
PartialProgramOrder.handle_complete
PartialProgramOrder.handle_wait
Expand Down
6 changes: 3 additions & 3 deletions docs/pdb.patch
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,10 @@ index 0f692e96..b60db641 100644

start_server()
{
- python3 -S "$PASH_SPEC_TOP/parallel-orch/scheduler_server.py" "$@" &
- python3 -S "$PASH_SPEC_TOP/scheduler/scheduler_server.py" "$@" &
- export daemon_pid=$!
- ## Wait until daemon has established connection
+ python3 -S "$PASH_SPEC_TOP/parallel-orch/scheduler_server.py" "$@"
+ python3 -S "$PASH_SPEC_TOP/scheduler/scheduler_server.py" "$@"
+ # export daemon_pid=$!
+ # # Wait until daemon has established connection
+ # pash_spec_wait_until_scheduler_listening
Expand Down Expand Up @@ -96,7 +96,7 @@ index 8569e55..0852d1a 100755
@@ -35,7 +29,9 @@ export PASH_SPEC_SCHEDULER_SOCKET="${PASH_SPEC_TMP_PREFIX}/scheduler_socket"

## TODO: Replace this with a call to pa.sh (which will start the scheduler on its own).
# python3 "$PASH_SPEC_TOP/parallel-orch/orch.py" "$@"
# python3 "$PASH_SPEC_TOP/scheduler/scheduler_server.py" "$@"
-"$PASH_TOP/pa.sh" --speculative "$@"
+a=$1
+shift
Expand Down
File renamed without changes.
42 changes: 14 additions & 28 deletions parallel-orch/executor.py → executor/executor.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import config
"""Executor component — launches commands in sandboxes and traces them."""

import logging
import subprocess
import util
import os

from dataclasses import dataclass
from executor_util import ptempfile, ptempdir, create_sandbox, copy, PASH_SPEC_TOP


@dataclass
class ExecCtxt:
Expand All @@ -30,53 +32,46 @@ class ExecArgs:
speculate_mode: bool
lower_sandboxes: list[str]

# This module executes a sequence of commands
# and traces them with Riker.
# All commands are run inside an overlay sandbox.

def set_pgid():
os.setpgid(0, 0)

def run_assignment_and_return_env_file(assignment: str, pre_execution_env_file: str):
post_execution_env_file = util.ptempfile(prefix='hs_assignment_post_env')
post_execution_env_file = ptempfile(prefix='hs_assignment_post_env')
logging.debug(f'Running assignment: {assignment} | pre_execution_env_file: {pre_execution_env_file} | post_execution_env_file: {post_execution_env_file}')
run_script = f'{config.PASH_SPEC_TOP}/parallel-orch/run_assignment.sh'
run_script = f'{PASH_SPEC_TOP}/executor/run_assignment.sh'
args = ["/bin/bash", run_script, assignment, pre_execution_env_file, post_execution_env_file]
process = subprocess.run(args, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
util.copy(pre_execution_env_file + '.fds', post_execution_env_file + '.fds')
copy(pre_execution_env_file + '.fds', post_execution_env_file + '.fds')
return post_execution_env_file

def run_trace_sandboxed(args: ExecArgs):
run_script = f'{config.PASH_SPEC_TOP}/parallel-orch/run_command.sh'
run_script = f'{PASH_SPEC_TOP}/executor/run_command.sh'

trace_file = util.ptempfile(prefix='hs_trace')
outfiles_dir = util.ptempdir(prefix='hs_outfiles')
stderr_file = util.ptempfile(prefix='hs_stderr')
trace_file = ptempfile(prefix='hs_trace')
outfiles_dir = ptempdir(prefix='hs_outfiles')
stderr_file = ptempfile(prefix='hs_stderr')
logging.debug(f'Scheduler: Trace file for: {args.concrete_node_id}: {trace_file}')
logging.debug(f'Scheduler: Stdout file for: {args.concrete_node_id} is: {outfiles_dir}')
logging.debug(f'Scheduler: Stderr file for: {args.concrete_node_id} is: {stderr_file}')

sandbox_dir, tmp_dir = util.create_sandbox()
post_execution_env_file = util.ptempfile(prefix='hs_post_env')
sandbox_dir, tmp_dir = create_sandbox()
post_execution_env_file = ptempfile(prefix='hs_post_env')
lower_dirs_str = ':'.join(args.lower_sandboxes)
speculate_mode = "speculate" if args.speculate_mode else "standard"

cmd = ["/bin/bash", run_script, args.command, trace_file, outfiles_dir, args.pre_execution_env_file, sandbox_dir, tmp_dir, speculate_mode, str(args.concrete_node_id), post_execution_env_file, str(args.execution_id), lower_dirs_str ]
logging.debug(cmd)
process = subprocess.Popen(cmd, stdout=None, stderr=None, preexec_fn=set_pgid)
# For debugging
# process = subprocess.Popen(cmd)

return ExecCtxt(process, trace_file, outfiles_dir, stderr_file, args.pre_execution_env_file, post_execution_env_file, sandbox_dir)

def commit_workspace(workspace_path):
## Call commit-sandbox.sh to commit the uncommitted sandbox to the main workspace
run_script = f'{config.PASH_SPEC_TOP}/deps/try/try'
run_script = f'{PASH_SPEC_TOP}/deps/try/try'
args = ["/bin/bash", run_script, "-i", "/run/mount", "commit", workspace_path]
process = subprocess.check_output(args)
return process

## Read trace and capture each command
def read_trace(sandbox_dir, trace_file):
if sandbox_dir == "":
path = trace_file
Expand All @@ -85,12 +80,3 @@ def read_trace(sandbox_dir, trace_file):
logging.debug(f'Reading trace from: {path}')
with open(path) as f:
return f.read().split('\n')[:-1]

def read_env_file(env_file, sandbox_dir=None):
if sandbox_dir is None:
path = env_file
else:
path = f"{sandbox_dir}/upperdir/{env_file}"
logging.debug(f'Reading env from: {path}')
out = subprocess.check_output([f"{os.getenv('PASH_TOP')}/compiler/orchestrator_runtime/pash_filter_vars.sh", path])
return out.decode("utf-8")
Loading
Loading