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

Detect and display non-zero returncode from scripts #474

Merged
merged 10 commits into from
Oct 19, 2023

Conversation

will-moore
Copy link
Member

@will-moore will-moore commented May 24, 2023

This uses the returncode returned from running scripts to show the Error icon in Activities dialog:

Feature requested at https://forum.image.sc/t/omero-scripts-fail-icon/81495

To test:

  • Run a script that throws an Exception after setting message, e.g. client.setOutput("Message", rstring("OK so far..."))
    • The Message will be shown, along with Error status (first result below)
  • Run a script that throws an Exception without setting "Message" (typical behaviour)
    • This will display a message including the returncode from the failure.
  • Otherwise working scripts should behave as before (2 other examples below)

Screenshot 2023-10-18 at 10 24 08

The context used to render the html page in the example above looks like this:

{
  "sizeOfJobs": 4,
  "jobs": [
    {
      "job_type": "script",
      "job_name": "Images From ROIs",
      "start_time": "datetime.datetime(2023, 10, 18, 10, 15, 20, 121058)",
      "status": "finished",
      "returncode": 1,
      "Message": "OK so far...",
      "stderr": 3725,
      "results": {},
      "id": "52dd4857-9ca1-4dd1-ad0b-5a78d3fdd235",
      "key": "ProcessCallback/52dd4857-9ca1-4dd1-ad0b-5a78d3fdd235 -t -e 1.1:tcp -h 172.19.0.4 -p 37387 -t 60000",
      "new": true
    },
    {
      "job_type": "script",
      "job_name": "Dataset To Plate",
      "start_time": "datetime.datetime(2023, 10, 18, 10, 16, 0, 665853)",
      "status": "finished",
      "returncode": 1,
      "Message": "Script exited with failure. (returncode=1)",
      "stderr": 3724,
      "results": {},
      "id": "087c20ed-0617-4931-9ef4-91046bd1181e",
      "key": "ProcessCallback/087c20ed-0617-4931-9ef4-91046bd1181e -t -e 1.1:tcp -h 172.19.0.4 -p 37387 -t 60000"
    },
    {
      "job_type": "script",
      "job_name": "Dataset To Plate",
      "start_time": "datetime.datetime(2023, 10, 18, 10, 15, 20, 121058)",
      "status": "finished",
      "returncode": 0,
      "Message": " New plate created: From_ROIs but could not be attached.",
      "results": {
        "New_Object": {
          "id": 101,
          "type": "Plate",
          "browse_url": "/webclient/userdata/?show=plate-101",
          "name": "From_ROIs"
        }
      },
      "id": "e31c309c-947c-4fa7-a8ff-1471f31a52d1",
      "key": "ProcessCallback/e31c309c-947c-4fa7-a8ff-1471f31a52d1 -t -e 1.1:tcp -h 172.19.0.4 -p 37387 -t 60000"
    },
    {
      "job_type": "script",
      "job_name": "Channel Offsets",
      "start_time": "datetime.datetime(2023, 10, 18, 10, 10, 54, 262510)",
      "status": "finished",
      "returncode": 0,
      "Message": "New image created: P-TRE_10_R3D_D3D.dv_0_offsets.",
      "stdout": 3719,
      "results": {
        "Image": {
          "id": 751,
          "type": "Image",
          "browse_url": "/webclient/userdata/?show=image-751",
          "name": "P-TRE_10_R3D_D3D.dv_0_offsets"
        }
      },
      "id": "6447e666-fc04-42ff-955c-d1cfe2e23035",
      "key": "ProcessCallback/6447e666-fc04-42ff-955c-d1cfe2e23035 -t -e 1.1:tcp -h 172.19.0.4 -p 37387 -t 60000"
    }
  ],
  "inprogress": 0,
  "new_results": 1,
  "new_errors": true,
  "failure": 0
}

@imagesc-bot
Copy link

This pull request has been mentioned on Image.sc Forum. There might be relevant details there:

https://forum.image.sc/t/omero-scripts-fail-icon/81495/7

if cb.returncode != 0:
# This 'error' shows failure icon
kwargs["error"] = 1
kwargs["Message"] = "Script failed with Exception"
Copy link
Member

Choose a reason for hiding this comment

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

Should this not just be kwargs["error"] = ...

Also, I could see adding the return code into the message unobstruvisely, e.g.:

f"Script exited with failure. (rc={{ cb.returnCode }})"

Copy link
Member Author

Choose a reason for hiding this comment

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

It is kwargs["error"] = ... ?
Do you mean kwargs["error"] = cb.returncode ?

@sbesson
Copy link
Member

sbesson commented May 24, 2023

Interesting thread which incidentally raises the question: what the recommendation should be for handling and communicating failures in OMERO.scripts?

At the moment, the primary relevant place I found in the reference documentation suggests failures may be handled via client output message - see https://omero.readthedocs.io/en/stable/developers/scripts/style-guide.html#script-outputs

Using the official Populate_Metadata.py OMERO script as an exmple, there is already a mixture of behaviours:

@ome ome deleted a comment from snoopycrimecop May 31, 2023
@ome ome deleted a comment from snoopycrimecop May 31, 2023
@ome ome deleted a comment from snoopycrimecop May 31, 2023
@ome ome deleted a comment from snoopycrimecop May 31, 2023
@will-moore
Copy link
Member Author

@sbesson Thanks for the review. I guess we can't currently have the script raise an Exception (to get the failed icon) and also return a useful Error message. But the Exception will be captured in stdout - do we need to make it more obvious that the user should look there?

I would prefer not to add another convention (on top of the "Message" convention) for errors, so I think that raising an Exception should be the best way to indicate "Failure".

# check if we get something back from the handle...
if cb.block(0): # ms.
cb.close()
try:
# we can only retrieve this ONCE - must save results
results = proc.getResults(0, conn.SERVICE_OPTS)
update_callback(request, cbString, status="finished")
kwargs = {
"status": ("finished" if cb.returncode == 0 else "failed"),
Copy link
Member

Choose a reason for hiding this comment

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

I initially thought that this key was reflecting the process termination state i.e. the enums defined in https://github.com/ome/omero-py/blob/d5ca72f930d85ee1f147f9f8e3cfd1c95c8089ae/src/omero/callbacks.py#L62-L64 but looking at the code, we seem to have additional state e.g. in-progress, failed etc

Do we have a list of all supported statuses with their meaning?
In that case, I suspect finished must be interpreted as finished with a zero return code. Note this is a more restrictive interpretation than https://docs.openmicroscopy.org/omero-blitz/5.6.2/slice2html/omero/grid/ProcessCallback.html#processFinished.

Copy link
Member Author

Choose a reason for hiding this comment

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

From testing in omero-figure, that app is using the "finished" status to know when the figure export script is done. Without it, the spinner keeps on spinning and the stderr isn't displayed.
So, best not to change this if we can help it.

}
if cb.returncode != 0:
# This 'error' shows failure icon
kwargs["error"] = 1
Copy link
Member

Choose a reason for hiding this comment

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

Reading the code, if return code is non-zero, there are currently three keys which have updated values (failure, status and error). Are these all necessary?

Copy link
Member Author

Choose a reason for hiding this comment

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

failure isn't used anywhere so I'll remove it.
The error flag is a boolean, so we could remove it and add set status = "error" for scripts.

kwargs["Message"] = (
f"Script exited with failure."
f" (returncode={ cb.returncode })"
)
Copy link
Member

Choose a reason for hiding this comment

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

What would happen if a script first set a message output via client.setOutput and then terminate with a non-zero return code?
Should we handle this use case and respect the message sent by the script and use this as the default error is unset?

Copy link
Member Author

Choose a reason for hiding this comment

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

@sbesson Just returning to this... In my testing, if the script does client.setOutput(....) then generates some un-caught exception and terminates, the output is not passed on to the server. I don't see it in the code at

for key, value in results.items():

Copy link
Member Author

Choose a reason for hiding this comment

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

Apologies - made a mistake in my client.setOutput("Message"...). That is actually handled OK and is displayed in place of the error message:

Screenshot 2023-09-08 at 14 37 37

However, most scripts don't try to return a "Message" until the end, so it's likely that if a script fails with some uncaught exception then the "Message" won't have been set.

@will-moore
Copy link
Member Author

That last commit fixed the display of Error reporting in Figure:

Screenshot 2023-10-11 at 14 25 06

@knabar knabar requested review from sbesson and joshmoore October 17, 2023 18:31
@knabar knabar added this to the 5.23.0 milestone Oct 17, 2023
@sbesson
Copy link
Member

sbesson commented Oct 17, 2023

@will-moore following up on the questions from #474 (comment), would it be possible to either update the description of this PR or add a comment with the different types of scenarios that should be tested and the expected outcome of the callback dictionary ?

@will-moore
Copy link
Member Author

@sbesson I've updated the PR description. Hope that contains everything you need?

@sbesson
Copy link
Member

sbesson commented Oct 18, 2023

Thanks @will-moore for adjusting the description. As per the previous discussion, I am still questioning the added value of the new failure key as its value seems to be completely redundant with the presence/value of error. I thought the plan was to remove it as per #474 (comment) ?

Also re #474 (comment), has the proposal of storing the value of the return code in error and adjust the HTML template to handle the non-zero scenario been discussed further?

@will-moore
Copy link
Member Author

OK - yes, apologies. I'll do that...

@will-moore
Copy link
Member Author

@sbesson Use key returncode now for the returncode! Updated the JSON in the description accordingly.

Copy link
Member

@sbesson sbesson left a comment

Choose a reason for hiding this comment

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

Tested with a minimal script

#!/usr/bin/env python
# -*- coding: utf-8 -*-

import omero.scripts as scripts
from omero.gateway import BlitzGateway
from omero.rtypes import rstring

def run_script():
    client = scripts.client(
        'test_failure.py',
        """Test""",
        scripts.Bool(
            "Fail", optional=False, grouping="1", default="False"),
        scripts.String(
            "Message", optional=True, grouping="2",
            description="The message to be sent to the client."),
    )

    try:
        conn = BlitzGateway(client_obj=client)
        script_params = client.getInputs(unwrap=True)
        
        if "Message" in script_params:
            client.setOutput("Message", rstring(script_params["Message"]))
        if script_params["Fail"]:
            raise Exception("This should fail the script")
    finally:
        client.closeSession()


if __name__ == "__main__":
    run_script()

After running the script with various conditions of failures/messages

Screenshot 2023-10-19 at 12 39 47

The underlying changes should remain backwards-compatible for existing apps which would rely on the context and make the return code available.
Overall, communicating a non-zero return code is definitely useful and I think this change is safe for inclusion in the next minor release of OMERO.web.

@knabar knabar merged commit 3a5d88d into ome:master Oct 19, 2023
7 checks passed
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.

5 participants