Skip to content

Commit

Permalink
supports abort task
Browse files Browse the repository at this point in the history
  • Loading branch information
qingeng authored and momchil-flex committed Jul 20, 2023
1 parent a9cce22 commit 8fd8a15
Show file tree
Hide file tree
Showing 8 changed files with 71 additions and 23 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

### Added
- Configuration option `config.log_suppression` can be used to control the suppression of log messages.
- `abort()` for `Job` and `mode solver`, Job or mode solver whose status is not success or error(e.g. running, draft) can be aborted, if Job or mode solver is abort, it can't be submitted, a new one needs to be created and submitted.
- `web.abort()` and `Job.abort()` methods allowing to abort running tasks without deleting them. If a task is aborted, it cannot be restarted later, a new one needs to be created and submitted.

### Changed
- Surface integration monitor validator changed to error only if *all* integration surfaces are outside of the simulation domain.
Expand Down
32 changes: 20 additions & 12 deletions tests/test_web/test_webapi.py
Original file line number Diff line number Diff line change
@@ -1,22 +1,19 @@
# Tests webapi and things that depend on it
import tempfile
import pytest
import responses
from _pytest import monkeypatch
import os

import tidy3d as td
import tidy3d.web as web

from responses import matchers

from tidy3d.exceptions import SetupError
from tidy3d.web.environment import Env
from tidy3d.web.webapi import delete, delete_old, download, download_json, run
from tidy3d.web.webapi import delete, delete_old, download, download_json, run, abort
from tidy3d.web.webapi import download_log, estimate_cost, get_info, get_run_info, get_tasks
from tidy3d.web.webapi import load, load_simulation, start, upload, monitor, real_cost
from tidy3d.web.container import Job, Batch
from tidy3d.web.task import TaskInfo
from tidy3d.web.asynchronous import run_async

from tidy3d.__main__ import main
Expand Down Expand Up @@ -151,12 +148,10 @@ def mock_start(monkeypatch, set_api_key, mock_get_info):

@pytest.fixture
def mock_monitor(monkeypatch):

status_count = [0]
statuses = ("upload", "running", "running", "running", "running", "running", "success")

def mock_get_status(task_id):

current_count = min(status_count[0], len(statuses) - 1)
current_status = statuses[current_count]
status_count[0] += 1
Expand Down Expand Up @@ -288,7 +283,6 @@ def mock_download(*args, **kwargs):

@responses.activate
def test_delete(set_api_key, mock_get_info):

responses.add(
responses.DELETE,
f"{Env.current.web_api_endpoint}/tidy3d/tasks/{TASK_ID}",
Expand Down Expand Up @@ -382,7 +376,6 @@ def test_delete_old(set_api_key):

@responses.activate
def test_get_tasks(set_api_key):

responses.add(
responses.GET,
f"{Env.current.web_api_endpoint}/tidy3d/project",
Expand Down Expand Up @@ -418,12 +411,30 @@ def test_real_cost(mock_get_info):
assert real_cost(TASK_ID) == FLEX_UNIT


@responses.activate
def test_abort_task(set_api_key):
responses.add(
responses.PUT,
f"{Env.current.web_api_endpoint}/tidy3d/tasks/abort",
match=[
matchers.json_params_matcher(
{
"taskId": TASK_ID,
"taskType": "FDTD",
}
)
],
json={"result": True},
status=200,
)
abort(TASK_ID)


""" Containers """


@responses.activate
def test_job(mock_webapi, monkeypatch):

monkeypatch.setattr("tidy3d.web.container.Job.load", lambda *args, **kwargs: True)
sim = make_sim()
j = Job(simulation=sim, task_name=TASK_NAME, folder_name=PROJECT_NAME)
Expand All @@ -444,7 +455,6 @@ def mock_job_status(monkeypatch):

@responses.activate
def test_batch(mock_webapi, mock_job_status):

# monkeypatch.setattr("tidy3d.web.container.Batch.monitor", lambda self: time.sleep(0.1))
# monkeypatch.setattr("tidy3d.web.container.Job.status", property(lambda self: "success"))

Expand All @@ -460,7 +470,6 @@ def test_batch(mock_webapi, mock_job_status):

@responses.activate
def test_async(mock_webapi, mock_job_status):

# monkeypatch.setattr("tidy3d.web.container.Job.status", property(lambda self: "success"))
sims = {TASK_NAME: make_sim()}
batch_data = run_async(sims, folder_name=PROJECT_NAME)
Expand All @@ -471,7 +480,6 @@ def test_async(mock_webapi, mock_job_status):

@responses.activate
def test_main(mock_webapi, monkeypatch, mock_job_status):

# sims = {TASK_NAME: make_sim()}
# batch_data = run_async(sims, folder_name=PROJECT_NAME)

Expand Down
6 changes: 6 additions & 0 deletions tidy3d/plugins/mode/web.py
Original file line number Diff line number Diff line change
Expand Up @@ -331,6 +331,12 @@ def delete(self):
# Delete parent task
http.delete(f"tidy3d/tasks/{self.task_id}")

def abort(self):
"""Abort the mode solver and its corresponding task from the server."""
return http.put(
"tidy3d/tasks/abort", json={"taskType": "MODE_SOLVER", "taskId": self.solver_id}
)

def get_modesolver(
self,
to_file: str = "mode_solver.json",
Expand Down
13 changes: 12 additions & 1 deletion tidy3d/web/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,18 @@
import sys

from .cli.migrate import migrate
from .webapi import run, upload, get_info, start, monitor, delete, download, load, estimate_cost
from .webapi import (
run,
upload,
get_info,
start,
monitor,
delete,
abort,
download,
load,
estimate_cost,
)
from .webapi import get_tasks, delete_old, download_json, download_log, load_simulation, real_cost
from .container import Job, Batch, BatchData
from .cli import tidy3d_cli
Expand Down
3 changes: 0 additions & 3 deletions tidy3d/web/container.py
Original file line number Diff line number Diff line change
Expand Up @@ -392,7 +392,6 @@ def _upload(cls, val, values) -> None:
verbose = bool(values.get("verbose"))
jobs = {}
for task_name, simulation in values.get("simulations").items():

# pylint:disable=protected-access
upload_kwargs = {key: values.get(key) for key in JobType._upload_fields}
upload_kwargs["task_name"] = task_name
Expand Down Expand Up @@ -485,7 +484,6 @@ def pbar_description(task_name: str, status: str) -> str:
)

with Progress(console=console) as progress:

# create progressbars
pbar_tasks = {}
for task_name, job in self.jobs.items():
Expand Down Expand Up @@ -616,7 +614,6 @@ def load(self, path_dir: str = DEFAULT_DATA_DIR) -> BatchData:
task_paths = {}
task_ids = {}
for task_name, job in self.jobs.items():

if "error" in job.status:
log.warning(f"Not loading '{task_name}' as the task errored.")
continue
Expand Down
2 changes: 1 addition & 1 deletion tidy3d/web/http_management.py
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@ def post(self, path: str, json=None):
def put(self, path: str, json=None, files=None):
"""Update the resource."""
return self.session.put(
Env.current.get_real_url(path), data=json, auth=api_key_auth, files=files
Env.current.get_real_url(path), json=json, auth=api_key_auth, files=files
)

@http_interceptor
Expand Down
6 changes: 6 additions & 0 deletions tidy3d/web/simulation_task.py
Original file line number Diff line number Diff line change
Expand Up @@ -537,3 +537,9 @@ def get_log(
verbose=verbose,
progress_callback=progress_callback,
)

def abort(self):
"""Abort current task from server."""
if not self.task_id:
raise ValueError("Task id not found.")
return http.put("tidy3d/tasks/abort", json={"taskType": "FDTD", "taskId": self.task_id})
30 changes: 24 additions & 6 deletions tidy3d/web/webapi.py
Original file line number Diff line number Diff line change
Expand Up @@ -305,7 +305,7 @@ def monitor(task_id: TaskId, verbose: bool = True) -> None:
task_info = get_info(task_id)
task_name = task_info.taskName

break_statuses = ("success", "error", "diverged", "deleted", "draft")
break_statuses = ("success", "error", "diverged", "deleted", "draft", "abort")

console = Console() if verbose else None

Expand Down Expand Up @@ -384,15 +384,14 @@ def monitor_preprocess() -> None:

# while running but percentage done is available
if verbose:

# verbose case, update progressbar
console.log("running solver")
console.log(
"To cancel the simulation, use 'web.delete(task_id)' or delete the task in the web"
"To cancel the simulation, use 'web.abort(task_id)' or 'web.delete(task_id)' "
"or abort/delete the task in the web"
" UI. Terminating the Python script will not stop the job running on the cloud."
)
with Progress(console=console) as progress:

pbar_pd = progress.add_task("% done", total=100)
perc_done, _ = get_run_info(task_id)

Expand All @@ -409,7 +408,6 @@ def monitor_preprocess() -> None:
progress.update(pbar_pd, completed=100, refresh=True)

else:

# non-verbose case, just keep checking until status is not running or perc_done >= 100
perc_done, _ = get_run_info(task_id)
while perc_done is not None and perc_done < 100 and get_status(task_id) == "running":
Expand All @@ -418,7 +416,6 @@ def monitor_preprocess() -> None:

# post processing
if verbose:

status = get_status(task_id)
if status != "running":
console.log(f"status = {status}")
Expand Down Expand Up @@ -677,6 +674,27 @@ def delete_old(
return len(tasks)


@wait_for_connection
def abort(task_id: TaskId) -> TaskInfo:
"""Abort server-side data associated with task.
Parameters
----------
task_id : str
Unique identifier of task on server. Returned by :meth:`upload`.
Returns
-------
TaskInfo
Object containing information about status, size, credits of task.
"""

# task = SimulationTask.get(task_id)
task = SimulationTask(taskId=task_id)
task.abort()
return TaskInfo(**{"taskId": task.task_id, **task.dict()})


# TODO: make this return a list of TaskInfo instead?
@wait_for_connection
def get_tasks(
Expand Down

0 comments on commit 8fd8a15

Please sign in to comment.