Tutorials

The purpose of the tutorials in this section is to explore basic features of the Queue Server. The tutorials also contain brief explanations of Queue Server features and API and recommendations on how and when they should be used.

Most tutorials require two terminals: one terminal is used to start Run Engine (RE) manager and display logging messages and console output from plans; the other terminal is used to interact with RE Manager by running CLI commands.

The RE Manager is the central component of the Queue Server, which maintains the queue of plans, creates RE Worker environment (a process where plans are executed) and provides flexible low level 0MQ API for client applications. The following tutorials provide instructions on how to interact with RE Manager using qserver CLI tool, which may be considered a basic client application. qserver functionality is limited to sending single 0MQ API requests and displaying received responses, but it provides direct access to almost every low level API (except batch queue operations). Use qserver -h to display brief help and full set of options or read the manual at qserver.

In production systems, users are more likely to interact with RE Manager using custom GUI applications or Bluesky Queue Server API running in IPython, but qserver may still be a convenient tool for exploring or demonstrating Queue Server features or testing and debugging deployed systems. The instructions on how to interact with RE Manager using qserver tool are also also useful for understanding the low-level 0MQ API or higher-level 0MQ/HTTP Bluesky Queue Server API, which could be used for development of Python applications. Lists of used 0MQ API with links to documentation are included at the end of each tutorial.

The following tutorials are available:

Starting the Queue Server

In the first terminal, start RE Manager as a console application:

$ start-re-manager
[I 2022-02-17 06:54:18,077 bluesky_queueserver.manager.manager] Starting ZMQ server at 'tcp://*:60615'
[I 2022-02-17 06:54:18,077 bluesky_queueserver.manager.manager] ZMQ control channels: encryption disabled
[I 2022-02-17 06:54:18,080 bluesky_queueserver.manager.manager] Starting RE Manager process
[I 2022-02-17 06:54:18,096 bluesky_queueserver.manager.manager] Loading the lists of allowed plans and devices ...
[I 2022-02-17 06:54:18,341 bluesky_queueserver.manager.manager] Starting ZeroMQ server ...
[I 2022-02-17 06:54:18,343 bluesky_queueserver.manager.manager] ZeroMQ server is waiting on tcp://*:60615

RE Manager functionality may be customized using CLI parameters. The default settings are selected specifically for running simple demonstrations, so RE Manager may be started without parameters in most of the tutorials. Type start-re-manager -h to display the full set of supported parameters. More detailed description may be found in the application manual.

The console output of RE Manager contains logging messages of the server and Bluesky and text output of the executed plans (such as Live Tables). RE Manager may be configured to publish console output to 0MQ socket so that it could be streamed to other applications (see Remote Monitoring of RE Manager Console Output). Production deployments of the Queue Server are likely to run RE Manager as a service, but starting it as an console application is very simple and recommended for tutorials, demonstrations and software development and testing.

The easiest way to test if the Queue Server is running and accessible is to call the status API:

$ qserver status
06:55:49 - MESSAGE:
{'devices_allowed_uid': '42ebeb34-cc00-41ff-96ec-9cb4210d0b10',
'devices_existing_uid': 'adc31393-3604-4765-b7c0-be25da34b9ec',
'items_in_history': 6,
'items_in_queue': 5,
'manager_state': 'idle',
'msg': 'RE Manager',
'pause_pending': False,
'plan_history_uid': '658ac3e5-ece3-4947-833f-293f8ec27687',
'plan_queue_mode': {'loop': False},
'plan_queue_uid': 'd409d889-1788-4da6-8af8-05456f63401c',
'plans_allowed_uid': '18a7fcf5-fba7-41e8-9872-4379f0537ec9',
'plans_existing_uid': 'ebad3d13-2106-4e38-89b3-2bc513f3576a',
'queue_stop_pending': False,
're_state': None,
'run_list_uid': 'e9d8449a-4635-42a0-bf8f-2af87d020e67',
'running_item_uid': None,
'task_results_uid': 'b281bed4-2b90-4f31-b8c7-64f2626927f1',
'worker_background_tasks': 0,
'worker_environment_exists': False,
'worker_environment_state': 'closed'}

The server always accepts status API requests and returns the set of parameters that reflects current state of RE Manager. For example, 'manager_state': 'idle' indicates current state of RE Manager and 'worker_environment_exists': False indicates if RE Worker environment is open and the server is ready to execute plans (currently the environment does not exist). Timeout occurs if the server is not accessible or does not respond in time. The detailed reference to RE Manager API could be found in the section Supported Methods For ZMQ Communication API. For example, documentation for the status API can be found here.

RE Manager application can be stopped at any time by activating pressing Ctrl-C.

Note

When RE Manager is closed using Ctrl-C key combination, execution of any plans, tasks, queue operations etc. is interrupted without warning or asking for confirmation. There is no risk of accidentally stopping RE Manager when it is running as a service.

API used in this tutorial: ‘status’.

Opening and Closing RE Worker Environment

Start RE Manager using instructions given in Starting the Queue Server.

In response to the request to open RE Worker Environment, RE Manager creates a new RE Worker process (for executing Bluesky plans), configures Run Engine and loads startup code in the RE Worker namespace. RE Manager may load startup code represented as a set of startup files (IPython style), Python script or module. bluesky-queueserver package includes a set of startup files with simulated devices and plans sufficient for simple demos. RE Manager is loading the built-in startup code unless alternative location is specified (see Running RE Manager with Custom Startup Code).

Open the RE Worker environment using qserver CLI tool:

$ qserver environment open
07:06:00 - MESSAGE:
{'msg': '', 'success': True}

The returned parameters include the flag, which indicates if the request was accepted by the server ('success': True) and the error message ('msg': ''), which is an empty string if the request is accepted. The API request only initiates the process of opening the environment, which may take significant time. The returned result 'success': True does not mean that the environment was successfully loaded or loaded at all. To find if the environment was loaded, check the status of RE Manager:

$ qserver status
07:15:15 - MESSAGE:
{ ...
'manager_state': 'idle',
  ...
'worker_environment_exists': True,
'worker_environment_state': 'idle'}

The most likely reason for failure to open an environment is an exception raised in the startup code. Search the console output of RE Manager for error messages and traceback.

Repeated requests to open the environment are rejected by the server:

$ qserver environment open
07:47:59 - MESSAGE:
{'msg': 'RE Worker environment already exists.', 'success': False}

The environment must be opened before executing any plans. The request to start the plan queue is rejected if the environment closed. All queue operations, including adding/removing/moving plans, do not require open environment. The process of opening an environment may indirectly affect the queue operations, because it involves generating new lists of existing/allowed plans and devices based on loaded startup scripts (see Validation of Plans).

The operation of closing RE Worker environment involves orderly exit from the message processing loop and closing the worker process. Closing the environment is safe, since it may be executed only if no plans or foreground tasks are running. The requests are rejected if the environment is busy.

Close the RE Worker environment using qserver CLI tool:

$ qserver environment close
07:48:53 - MESSAGE:
{'msg': '', 'success': True}

The API request only initiates the process of closing the environment. Check RE Manager status to determine if the environment was closed successfully:

$ qserver status
07:15:15 - MESSAGE:
{ ...
'manager_state': 'idle',
  ...
'worker_environment_exists': False,
'worker_environment_state': 'closed'}

Repeated requests to close the environment are rejected:

$ qserver environment close
07:49:46 - MESSAGE:
{'msg': 'RE Worker environment does not exist.', 'success': False}

RE Worker Environment is designed to run user code in the form of Bluesky plans or user defined functions. If the main thread gets stuck in an infinite loop or inifinite wait (e.g. waits for non-responding PV without timeout), the environment may become unresponsive and can not be closed. This may cause substantial inconvenience during remote operation of the beamline. RE Manager supports an API that allow to recover from this state by destroying an unresponsive environment (killing the RE Worker process). After the environment is destroyed, a new environment may be opened and operation resumed. The operation of destroying an environment is unsafe, and accidentally sending the request during normal operation kills any running plans or tasks.

The process of destroying the RE Worker environment is initiated using the following command:

$ qserver environment destroy
07:50:25 - MESSAGE:
{'msg': '', 'success': True}

It may take a little time for the operation to complete. Check the status to verify that the environment is in closed state and RE Manager is idle.

API used in this tutorial: ‘status’, ‘environment_open’, ‘environment_close’, ‘environment_destroy’.

Adding Items to Queue

Queue operations, such as adding and removing items, replacing or moving existing items, may be performed at any time. The environment does not need to be opened to manipulate the queue. Queue Server performs validation of the submitted plans and rejects plans that do not exist or the plans that are not allowed to be executed by the user. Plans may accept devices as parameter values. The devices must be in the list of allowed devices for the user submitting the plan, otherwise the plan is rejected (if plan parameters are validated) or fail during plan execution.

Start RE Manager using instructions given in Starting the Queue Server.

Display the lists of allowed plans and devices. Note that the plans scan and count are in the list of allowed plans and det1, det2 and motor are in the list of allowed devices. qserver tool displays only the set top-level device names, but subdevice names can also be used as plan parameters:

$ qserver allowed plans
09:27:52 - MESSAGE:
{'msg': '',
'plans_allowed': {'adaptive_scan': '{...}',
                  'count': '{...}',
                  'count_bundle_test': '{...}',
                  ...
                  'relative_inner_product_scan': '{...}',
                  'scan': '{...}',
                  ...
                  'x2x_scan': '{...}'},
'plans_allowed_uid': '18a7fcf5-fba7-41e8-9872-4379f0537ec9',
'success': True}

$ qserver allowed devices
09:31:45 - MESSAGE:.;
{'devices_allowed': {'ab_det': '{...}',
                    ...
                    'det': '{...}',
                    'det1': '{...}',
                    'det2': '{...}',
                    'det3': '{...}',
                    'det4': '{...}',
                    'det5': '{...}',
                    ...
                    'motor': '{...}',
                    'motor1': '{...}',
                    'motor2': '{...}',
                    'motor3': '{...}',
                    ...
                    'sim_bundle_A': '{...}',
                    'sim_bundle_B': '{...}'},
'devices_allowed_uid': '42ebeb34-cc00-41ff-96ec-9cb4210d0b10',
'msg': '',
'success': True}

First let’s clear the queue, since it may already contain plans:

$ qserver queue clear
10:08:09 - MESSAGE:
{'msg': '', 'success': True}

Verify that the number of items in the queue is zero:

$ qserver status
10:08:25 - MESSAGE:
{ ...
'items_in_queue': 0,
... }

Load the contents of the queue (item), which should be empty at this point:

$ qserver queue get
10:08:35 - MESSAGE:
{'items': [],
'msg': '',
'plan_queue_uid': '5ae71b0f-c671-4ce3-93bb-b854296dd4f8',
'running_item': {},
'success': True}

Now let’s add the plan count([det1, det2], num=10, delay=2) to the queue:

$ qserver queue add plan '{"name": "count", "args": [["det1", "det2"]], "kwargs": {"num": 10, "delay": 1}}'
10:04:49 - MESSAGE:
{'item': {'args': [['det1', 'det2']],
          'item_type': 'plan',
          'item_uid': '0aa7f1be-3923-4d67-ba7b-b19d26ec6291',
          'kwargs': {'delay': 1, 'num': 10},
          'name': 'count',
          'user': 'qserver-cli',
          'user_group': 'primary'},
'msg': '',
'qsize': 1,
'success': True}

The submitted plan was accepted by the server and added to the queue. The parameter 'qsize': 1 shows the new size of the plan queue. Verify the queue size and load the updated queue:

$ qserver status
10:08:25 - MESSAGE:
{ ...
'items_in_queue': 1,
... }

$ qserver queue get
10:16:43 - MESSAGE:
{'items': [{'args': [['det1', 'det2']],
            'item_type': 'plan',
            'item_uid': 'af4169c0-1d9c-4412-ad0b-5a232e1b13e7',
            'kwargs': {'delay': 1, 'num': 10},
            'name': 'count',
            'user': 'qserver-cli',
            'user_group': 'primary'}],
'msg': '',
'plan_queue_uid': 'dfad1d60-abd9-4bd9-895c-10b7c2dc8897',
'running_item': {},
'success': True}

The items are added to the back of the queue by default. Let’s add another plan scan([det1, det2], motor, -1, 1, 10) to the queue:

$ qserver queue add plan '{"name": "scan", "args": [["det1", "det2"], "motor", -1, 1, 10]}'
10:21:37 - MESSAGE:
{'item': {'args': [['det1', 'det2'], 'motor', -1, 1, 10],
          'item_type': 'plan',
          'item_uid': '17e45208-b8d7-4545-9bd6-d6aa7263adc9',
          'name': 'scan',
          'user': 'qserver-cli',
          'user_group': 'primary'},
'msg': '',
'qsize': 2,
'success': True}

Note that the queue size is now 2. Load the list of queue items and verify that the scan plan is added to the back of the queue:

$ qserver queue get
10:24:24 - MESSAGE:
{'items': [{'args': [['det1', 'det2']],
            'item_type': 'plan',
            'item_uid': 'af4169c0-1d9c-4412-ad0b-5a232e1b13e7',
            'kwargs': {'delay': 1, 'num': 10},
            'name': 'count',
            'user': 'qserver-cli',
            'user_group': 'primary'},
          {'args': [['det1', 'det2'], 'motor', -1, 1, 10],
            'item_type': 'plan',
            'item_uid': '17e45208-b8d7-4545-9bd6-d6aa7263adc9',
            'name': 'scan',
            'user': 'qserver-cli',
            'user_group': 'primary'}],
'msg': '',
'plan_queue_uid': '29d6b8fe-7100-4bdc-b348-845cc2728d1b',
'running_item': {},
'success': True}

The RE Manager API supports an extensive set of options to define the location of inserted plans. For example a plan may be inserted to the front of the queue:

$ qserver queue add plan front '{"name": "scan", "args": [["det1"], "motor", -2, 2, 5]}'
10:29:09 - MESSAGE:
{'item': {'args': [['det1'], 'motor', -2, 2, 5],
          'item_type': 'plan',
          'item_uid': '3a6ae812-5d59-4f05-bfad-67e4f8a798e2',
          'name': 'scan',
          'user': 'qserver-cli',
          'user_group': 'primary'},
'msg': '',
'qsize': 3,
'success': True}

Verify that the new plan was inserted to the front of the queue:

$ qserver queue get
  10:30:00 - MESSAGE:
  {'items': [{'args': [['det1'], 'motor', -2, 2, 5],
              'item_type': 'plan',
              'item_uid': '3a6ae812-5d59-4f05-bfad-67e4f8a798e2',
              'name': 'scan',
              'user': 'qserver-cli',
              'user_group': 'primary'},
            {'args': [['det1', 'det2']],
              'item_type': 'plan',
              'item_uid': 'af4169c0-1d9c-4412-ad0b-5a232e1b13e7',
              'kwargs': {'delay': 1, 'num': 10},
              'name': 'count',
              'user': 'qserver-cli',
              'user_group': 'primary'},
            {'args': [['det1', 'det2'], 'motor', -1, 1, 10],
              'item_type': 'plan',
              'item_uid': '17e45208-b8d7-4545-9bd6-d6aa7263adc9',
              'name': 'scan',
              'user': 'qserver-cli',
              'user_group': 'primary'}],
  'msg': '',
  'plan_queue_uid': 'ba87dce1-c598-4a4a-a801-3e145e9b4365',
  'running_item': {},
  'success': True}

The queue may contain instructions that are executed by RE Manager and control execution of the queue. The only supported instruction is queue_stop, which stops execution of the queue (for example to let the operator change a sample). The queue can be restarted afterwards. The following command will insert the instruction before the element at position -2 in the queue:

$ qserver queue add instruction -2 queue-stop
10:36:31 - MESSAGE:
{'item': {'item_type': 'instruction',
          'item_uid': 'e2fcb2b6-a968-4e36-a345-47416b3814b0',
          'name': 'queue_stop',
          'user': 'qserver-cli',
          'user_group': 'primary'},
'msg': '',
'qsize': 4,
'success': True}

$ qserver queue get
10:36:40 - MESSAGE:
{'items': [{'args': [['det1'], 'motor', -2, 2, 5],
            'item_type': 'plan',
            'item_uid': '3a6ae812-5d59-4f05-bfad-67e4f8a798e2',
            'name': 'scan',
            'user': 'qserver-cli',
            'user_group': 'primary'},
          {'item_type': 'instruction',
            'item_uid': 'e2fcb2b6-a968-4e36-a345-47416b3814b0',
            'name': 'queue_stop',
            'user': 'qserver-cli',
            'user_group': 'primary'},
          {'args': [['det1', 'det2']],
            'item_type': 'plan',
            'item_uid': 'af4169c0-1d9c-4412-ad0b-5a232e1b13e7',
            'kwargs': {'delay': 1, 'num': 10},
            'name': 'count',
            'user': 'qserver-cli',
            'user_group': 'primary'},
          {'args': [['det1', 'det2'], 'motor', -1, 1, 10],
            'item_type': 'plan',
            'item_uid': '17e45208-b8d7-4545-9bd6-d6aa7263adc9',
            'name': 'scan',
            'user': 'qserver-cli',
            'user_group': 'primary'}],
'msg': '',
'plan_queue_uid': 'bc66304a-2cd3-430a-acae-1b2152b60dba',
'running_item': {},
'success': True}

Note, that using negative indices to address queue items (counting items from the back of the queue) is more reliable, since queue operations could be performed while the queue is running and items may be removed from the front of the queue at any moment. Alternatively, items may be addressed using item_uid, which is never changed by the queue operations.

API used in this tutorial: ‘status’, ‘queue_item_add’, ‘queue_get’, ‘queue_clear’, ‘plans_allowed’, ‘devices_allowed’.

Starting and Stopping the Queue

Start RE Manager using instructions given in Starting the Queue Server.

Clear the queue and add a few plans to the queue as described in Adding Items to Queue. For this tutorial, it is recommended to use plans that take relatively long time to execute. For example the following plan runs for about 20 seconds:

$ qserver queue add plan '{"name": "count", "args": [["det1", "det2"]], "kwargs": {"num": 10, "delay": 2}}'

In the following example we assume that the queue contains three count plans with the queue execution time about 60 seconds:

$ qserver queue get
13:07:40 - MESSAGE:
{'items': [{'args': [['det1', 'det2']],
            'item_type': 'plan',
            'item_uid': 'fffa482a-f655-4999-9e90-1d6550f67b72',
            'kwargs': {'delay': 2, 'num': 10},
            'name': 'count',
            'user': 'qserver-cli',
            'user_group': 'primary'},
          {'args': [['det1', 'det2']],
            'item_type': 'plan',
            'item_uid': '7426b43b-102f-42f1-a43e-2c3f2b9009a7',
            'kwargs': {'delay': 2, 'num': 10},
            'name': 'count',
            'user': 'qserver-cli',
            'user_group': 'primary'},
          {'args': [['det1', 'det2']],
            'item_type': 'plan',
            'item_uid': '859760ef-51ad-4861-832c-b113b008fa3e',
            'kwargs': {'delay': 2, 'num': 10},
            'name': 'count',
            'user': 'qserver-cli',
            'user_group': 'primary'}],
'msg': '',
'plan_queue_uid': 'a20c74fe-0888-4e61-9a37-4fbc9697fe3d',
'running_item': {},
'success': True}

Open the environment as described in Opening and Closing RE Worker Environment.

Every plan that is executed by RE Manager is added to the plan history. The history is not designed to for long-term storage and must be periodically cleared:

$ qserver history clear
11:51:11 - MESSAGE:
{'msg': '', 'success': True}

The number of items in the history is reported as part RE Manager status:

$ qserver status
12:01:14 - MESSAGE:
{ ...
'items_in_history': 0,
'items_in_queue': 3,
... }

Start the queue and observe the logging messages and Live Table displayed in the terminal running RE Manager ('success': True indicates that the request was accepted by the server and the queue is about to get started):

$ qserver queue start
12:05:15 - MESSAGE:
{'msg': '', 'success': True}

While the first plan is still running, check the contents of the queue: running_item is a dictionary of parameters of the currently running plan and items is a list of the plans remaining in the queue:

$ qserver queue get
Arguments: ['queue', 'get']
13:07:54 - MESSAGE:
{'items': [{'args': [['det1', 'det2']],
            'item_type': 'plan',
            'item_uid': '7426b43b-102f-42f1-a43e-2c3f2b9009a7',
            'kwargs': {'delay': 2, 'num': 10},
            'name': 'count',
            'user': 'qserver-cli',
            'user_group': 'primary'},
          {'args': [['det1', 'det2']],
            'item_type': 'plan',
            'item_uid': '859760ef-51ad-4861-832c-b113b008fa3e',
            'kwargs': {'delay': 2, 'num': 10},
            'name': 'count',
            'user': 'qserver-cli',
            'user_group': 'primary'}],
'msg': '',
'plan_queue_uid': '4948d6ba-586c-4a70-a1f9-f933124c1e58',
'running_item': {'args': [['det1', 'det2']],
                  'item_type': 'plan',
                  'item_uid': 'fffa482a-f655-4999-9e90-1d6550f67b72',
                  'kwargs': {'delay': 2, 'num': 10},
                  'name': 'count',
                  'user': 'qserver-cli',
                  'user_group': 'primary'},
'success': True}

Once all plans are completed, verify RE Manager status to make sure that the queue is empty and the correct number of plans were added to history:

$ qserver status
12:16:31 - MESSAGE:
{ ...
'items_in_history': 3,
'items_in_queue': 0,
... }

All functions for manipulating the queue are accessible while the queue is running. Add a few plans to the queue, start the queue and try adding plans to the queue while it is running. Check the contents of the queue (qserver queue get) to observe changes.

RE Manager supports an API that allows to stop execution of the queue after completion of the current plan. This API is intended to be used in cases when the currently running plan should be normally completed, but some intervention by the operator (e.g. adjustment of the sample) is needed before the next plan is started. The API call does not influence execution of currently running plan.

Add more plans to the queue and start the queue. While the first plan is running use the following command to stop the queue:

$ qserver queue stop
2:19:01 - MESSAGE:
{'msg': '', 'success': True}

While the plan is still running, check that the current state is reflected in the RE Manager status (queue_stop_pending):

$ qserver status
12:19:05 - MESSAGE:
{ ...
'manager_state': 'executing_queue',
...
'queue_stop_pending': True,
... }

Observe that the queue stops after the current plan is completed. Note, that the sequence of commands (qserver queue start, qserver queue stop and qserver status) must be issued while the plan is running. Increase the values of num or delay plan parameters to make the plan run longer if needed.

Since plans may take long time (potentially hours) to execute and an operator may send the API request to stop the queue by mistake or change the decision while the plan is running, RE Manager allows to cancel the pending request to stop the queue. Execute the following commands in rapid sequence while the plan is still running to observe change in queue_stop_pending status parameter:

$ qserver queue start

$ qserver queue stop

$ qserver status
12:41:38 - MESSAGE:
{ ...
'manager_state': 'executing_queue',
...
'queue_stop_pending': True,
... }

$ qserver queue stop cancel

$ qserver status
12:41:46 - MESSAGE:
{ ...
'manager_state': 'executing_queue',
...
'queue_stop_pending': False,
... }

Execution of the plans will continue until the queue is empty.

API used in this tutorial: ‘status’, ‘queue_start’, ‘queue_stop’, ‘queue_stop_cancel’, ‘history_clear’.

Interacting with Run Engine

RE Manager hides most of the low level details related to execution of plans, but some functionality relevant to Run Engine monitoring and control is accessible via 0MQ API:

  • Status parameters: re_state indicating current state of the Run Engine and pause_pending which indicates if deferred pause is pending at Run Engine.

  • 0MQ API for pausing, resuming, stopping, aborting or halting the running plan. See Bluesky documentation for more detailed information on how Run Engine is handling plan interruptions.

Run Engine is not instantiated if the RE Worker environment is closed and re_state is always None and pause_pending is False:

$ qserver status
14:59:09 - MESSAGE:
{ ...
'pause_pending': False,
...
're_state': None,
... }

If environment is open (see Opening and Closing RE Worker Environment), then re_state is a string that represents actual state of the Run Engine:

$ qserver status
16:19:30 - MESSAGE:
{ ...
'pause_pending': False,
...
're_state': 'idle',
... }

The operations that interrupt execution of currently running plan are handled by the Run Engine. RE Manager provides API for initiating plan interruptions, including pausing the plan, and then resuming, stopping, aborting or halting the paused plan. Note, that the API for stopping the queue and stopping the paused plan are not related, except that the queue is automatically stopped if the plan is stopped, aborted, halted or fails to complete in any other way.

It is assumed that the RE Worker environment is open. Add a plan to the queue. The following plan runs for one minute and should work well for the demonstration:

$ qserver queue add plan '{"name": "count", "args": [["det1", "det2"]], "kwargs": {"num": 6, "delay": 10}}'

count plan contains a checkpoint before each measurement. The API allow to initiate deferred and immediate pause. In case of deferred pause (equivalent to single Ctrl-C in IPython) the plan is executed until the next checkpoint, i.e. the current measurement is completed and the next measurement is started once the plan is resumed. In case of immediate pause (double Ctrl-C in IPython) the plan is rolled back to the previous checkpoint and the current measurement is repeated once the plan is resumed. The plan performs 6 measurments with the period of 10 seconds between measurements, so it is easy to observer how operations of pausing and resuming the plans works:

# Start the queue
$ qserver queue start

# Request the deferred pause
$ qserver re pause
16:59:59 - MESSAGE:
{'msg': '', 'success': True}

# Check status while the plan is still running, but deferred pause is pending
$ qserver status
Arguments: ['status']
{ ...
'manager_state': 'executing_queue',
...
'pause_pending': True,
...
're_state': 'running',
...}

# Check status again once the plan is paused (takes a few seconds to reach the next checkpoint)
$ qserver status
17:00:25 - MESSAGE:
{ ...
'manager_state': 'paused',
...
'pause_pending': False,
...
're_state': 'paused',
...}

# Resume the plan
$ qserver re resume
17:07:08 - MESSAGE:
{'msg': '', 'success': True}

The output of RE Manager contains the following Live Table. Note, that the measurement #1 was fully completed and not repeated after the plan was resumed:

Transient Scan ID: 1     Time: 2022-02-17 16:59:53
Persistent Unique Scan ID: 'fc9f444e-9a52-4df6-9486-a877f9022528'
New stream: 'primary'
+-----------+------------+------------+------------+
|   seq_num |       time |       det2 |       det1 |
+-----------+------------+------------+------------+
|         1 | 16:59:53.1 |      1.765 |      5.000 |
[I 2022-02-17 16:59:59,198 bluesky_queueserver.manager.manager] Pausing the queue (currently running plan) ...
[I 2022-02-17 16:59:59,198 bluesky_queueserver.manager.worker] Pausing Run Engine ...
Deferred pause acknowledged. Continuing to checkpoint.
Pausing...
[I 2022-02-17 17:07:08,353 bluesky_queueserver.manager.manager] Resuming paused plan ...
[I 2022-02-17 17:07:08,353 bluesky_queueserver.manager.worker] Run Engine: resume
[I 2022-02-17 17:07:08,353 bluesky_queueserver.manager.worker] Continue plan execution with the option 'resume'
|         2 | 17:07:08.3 |      1.765 |      5.000 |
|         3 | 17:07:08.3 |      1.765 |      5.000 |
|         4 | 17:07:18.3 |      1.765 |      5.000 |
|         5 | 17:07:28.3 |      1.765 |      5.000 |
|         6 | 17:07:38.3 |      1.765 |      5.000 |
Run was closed: 'fc9f444e-9a52-4df6-9486-a877f9022528'
+-----------+------------+------------+------------+
generator count ['fc9f444e'] (scan num: 1)

The following sequence of commands starts the queue and request immediate pause. The sequence may be tested with the same plan:

$ qserver start
$ qserver re pause immediate
$ qserver re resume

In the Live Table, measurement #2 was cancelled when the plan was paused and repeated after the plan was resumed:

Transient Scan ID: 2     Time: 2022-02-17 17:15:31
Persistent Unique Scan ID: '76e20bbc-e38c-40ab-a66f-f16745f9baf2'
New stream: 'primary'
+-----------+------------+------------+------------+
|   seq_num |       time |       det2 |       det1 |
+-----------+------------+------------+------------+
|         1 | 17:15:31.7 |      1.765 |      5.000 |
|         2 | 17:15:41.7 |      1.765 |      5.000 |
[I 2022-02-17 17:15:42,340 bluesky_queueserver.manager.manager] Pausing the queue (currently running plan) ...
[I 2022-02-17 17:15:42,341 bluesky_queueserver.manager.worker] Pausing Run Engine ...
Pausing...
[I 2022-02-17 17:15:52,403 bluesky_queueserver.manager.manager] Resuming paused plan ...
[I 2022-02-17 17:15:52,403 bluesky_queueserver.manager.worker] Run Engine: resume
[I 2022-02-17 17:15:52,403 bluesky_queueserver.manager.worker] Continue plan execution with the option 'resume'
|         2 | 17:15:52.4 |      1.765 |      5.000 |
|         3 | 17:16:02.4 |      1.765 |      5.000 |
|         4 | 17:16:12.4 |      1.765 |      5.000 |
|         5 | 17:16:22.4 |      1.765 |      5.000 |
|         6 | 17:16:32.5 |      1.765 |      5.000 |
Run was closed: '76e20bbc-e38c-40ab-a66f-f16745f9baf2'
+-----------+------------+------------+------------+
generator count ['76e20bbc'] (scan num: 2)

Once the plan is paused, it can be resumed (as alread demonstrated), stopped, aborted or halted. The technical difference between the three methods of terminating a plan relatively small, except that stopped plans is considered successful, aborted and halted plans are considered failed; a new plan can be started immediately after a plan is stopped or aborted, but the environment needs to be restarted (closed and opened again) after a plan is halted.

The respective qserver commands are

$ qserver re stop
$ qserver re abort
$ qserver re halt

API used in this tutorial: ‘status’, ‘re_pause’, ‘re_resume’, ‘re_stop’, ‘re_abort’, ‘re_halt’.

Immediate Execution of Plans

RE Manager allows to submit single plans for immediate execution without placing them in the queue or changing contents of the queue (see Immediate Execution of Plans). This tutorial demonstrates how to submit a single plan and retrieve the results of its execution.

Start RE Manager using instructions given in Starting the Queue Server.

Open the environment:

$ qserver environment open
Arguments: ['environment', 'open']
15:02:06 - MESSAGE:
{'msg': '', 'success': True}

Check the status of RE Manager:

$ qserver status
Arguments: ['status']
15:02:48 - MESSAGE:
{ ...
'manager_state': 'idle',
  ...
're_state': 'idle',
  ... }

The state of RE Manager and Run Engine is idle, which means that the plan will be accepted.

Now start the same count plan used in previous tutorials. Plan execution will start immediately:

$ qserver queue execute plan '{"name": "count", "args": [["det1", "det2"]], "kwargs": {"num": 10, "delay": 1}}'
Arguments: ['queue', 'execute', 'plan', '{"name": "count", "args": [["det1", "det2"]], "kwargs": {"num": 10, "delay": 1}}']
15:05:38 - MESSAGE:
{'item': {'args': [['det1', 'det2']],
          'item_type': 'plan',
          'item_uid': '8848ffde-bb83-4b60-b2d1-a4d2c12ce340',
          'kwargs': {'delay': 1, 'num': 10},
          'name': 'count',
          'user': 'qserver-cli',
          'user_group': 'primary'},
'msg': '',
'qsize': 0,
'success': True}

Check the last item in the plan history to make sure the plan was completed successfully. Compare item_uid of the plan accepted for execution with item_uid of the plan in history:

$ qserver history get
Arguments: ['history', 'get']
15:07:47 - MESSAGE:
{'items': [{'args': [['det1', 'det2']],
            'item_type': 'plan',
            'item_uid': '8848ffde-bb83-4b60-b2d1-a4d2c12ce340',
            'kwargs': {'delay': 1, 'num': 10},
            'name': 'count',
            'result': {'exit_status': 'completed',
                      'msg': '',
                      'run_uids': ['e0592906-2028-4ab5-8148-cefe234d96a7'],
                      'time_start': 1659467138.782897,
                      'time_stop': 1659467149.3967063,
                      'traceback': ''},
            'user': 'qserver-cli',
            'user_group': 'primary'}],
'msg': '',
'plan_history_uid': 'bfe5b3c7-3689-4f7c-ba31-89e23c7c0555',
'success': True}

API used in this tutorial: ‘status’, ‘queue_item_execute’, ‘queue_get’.

Executing Functions

RE Manager allows to initiate execution functions in RE Worker process (see Executing Functions). The demo startup code loaded by RE Manager by default defines function function_sleep, which accepts a single parameter defining execution time of the function and returns a dictionary containing success flag (always True) and the time value passed as the parameter. The default permissions for the demo are defined so that the primary user is allowed to call this function. The function is convenient for demonstration and testing, because it allows to set the time of function execution and see the time when the function starts and finishes by looking at the console output:

# Implementation of 'function_sleep' from the demo startup code

def function_sleep(time):
    """
    Sleep for a given number of seconds.
    """
    print("******** Starting execution of the function 'function_sleep' **************")
    print(f"*******************   Waiting for {time} seconds **************************")
    ttime.sleep(time)
    print("******** Finished execution of the function 'function_sleep' **************")

    return {"success": True, "time": time}

Start RE Manager, open the environment and verify that RE Manager is in idle state. Use the same steps as in Immediate Execution of Plans.

Start execution of the function. Long delay (60 seconds) allows sufficient time to experiment:

$ qserver function execute '{"name": "function_sleep", "args": [60], "kwargs": {}}'
Arguments: ['function', 'execute', '{"name": "function_sleep", "args": [60], "kwargs": {}}']
18:42:29 - MESSAGE:
{'item': {'args': [60],
          'item_uid': '6d23469a-94c3-4d4f-ad5a-dda4861515c7',
          'kwargs': {},
          'name': 'function_sleep',
          'user': 'qserver-cli',
          'user_group': 'primary'},
'msg': '',
'success': True,
'task_uid': '6d23469a-94c3-4d4f-ad5a-dda4861515c7'}

The function is now running as a foreground task. Check the status of RE Manager. Note, that manager_state and worker_environment_state is set as 'executing_task' and the number of background tasks running in the worker environment is 0:

$ qserver status
Arguments: ['status']
18:42:33 - MESSAGE:
{ ...
'manager_state': 'executing_task',
  ...
'worker_background_tasks': 0,
  ...
'worker_environment_state': 'executing_task'}

Check the status of the task, which is now returned as 'running':

$ qserver task status '6d23469a-94c3-4d4f-ad5a-dda4861515c7'
Arguments: ['task', 'status', '6d23469a-94c3-4d4f-ad5a-dda4861515c7']
18:42:44 - MESSAGE:
{'msg': '',
'status': 'running',
'success': True,
'task_uid': '6d23469a-94c3-4d4f-ad5a-dda4861515c7'}

Look at the console output of RE Manager and wait until function exits. Check task status again. It is now changed to 'completed':

$ qserver task status '6d23469a-94c3-4d4f-ad5a-dda4861515c7'
Arguments: ['task', 'status', '6d23469a-94c3-4d4f-ad5a-dda4861515c7']
18:43:33 - MESSAGE:
{'msg': '',
'status': 'completed',
'success': True,
'task_uid': '6d23469a-94c3-4d4f-ad5a-dda4861515c7'}

Now load the result of task execution. The return_value field represents the value returned by the function and must be serializable to JSON:

$ qserver task result '6d23469a-94c3-4d4f-ad5a-dda4861515c7'
Arguments: ['task', 'result', '6d23469a-94c3-4d4f-ad5a-dda4861515c7']
18:43:43 - MESSAGE:
{'msg': '',
'result': {'msg': '',
            'return_value': {'success': True, 'time': 60},
            'success': True,
            'task_uid': '6d23469a-94c3-4d4f-ad5a-dda4861515c7',
            'time_start': 1659480149.1098506,
            'time_stop': 1659480209.2685587,
            'traceback': ''},
'status': 'completed',
'success': True,
'task_uid': '6d23469a-94c3-4d4f-ad5a-dda4861515c7'}

Now start the same function as a background task:

$ qserver function execute '{"name": "function_sleep", "args": [60], "kwargs": {}}' background

and check the status:

$ qserver status
Arguments: ['status']
18:56:21 - MESSAGE:
{ ...
'manager_state': 'idle',
...
'worker_background_tasks': 1,
...
'worker_environment_state': 'idle'}

The status of the manager and the environment is now 'idle' and the number of background tasks is 1. The task status can be monitored using task UID as in the first example. Start the function again without waiting for the first instance of the function to complete:

$ qserver function execute '{"name": "function_sleep", "args": [60], "kwargs": {}}' background

and check the status. The number of background tasks is now 2:

$ qserver status
Arguments: ['status']
18:56:45 - MESSAGE:
{ ...
'manager_state': 'idle',
  ...
'worker_background_tasks': 2,
  ...
'worker_environment_state': 'idle'}

The manager and environment state is 'idle', which means that users are free to run plans or foreground tasks without waiting for the background tasks to complete. Background tasks can also be started while plans or forground tasks are running. Try running the function as a foreground task. Also try running a plan while the function is running. Also try running one or multiple copies of the function while a plan or a foreground task is running.

API used in this tutorial: ‘function_execute’, ‘status’, ‘task_status’, ‘task_result’.

Uploading scripts

RE Manager provides users with ability to upload and execute Python scripts in the worker namespace. See notes in section Uploading Scripts for more detailed description.

The qserver script upload CLI tool supports all the functionality of the ‘script_upload’ API. Instead of string representation of the script, it accepts a path to the script file as a parameter. Let’s create a simple script file (e.g. ‘test_script.py`) in the current directory:

# Add a simple plan
def count_test(detectors, *, num=1, delay=1):
    yield from count(detectors, num=num, delay=delay)

# Wait for some time to emulate the script with longer execution time
ttime.sleep(30)

The script adds a new plan count_test to the environment and then waits for 30 seconds to emulate long execution time. Start RE Manager, open the environment and verify that RE Manager is in idle state. Use the same steps as in Immediate Execution of Plans. Then check that the plan is not in the list of allowed plans:

$ qserver allowed plans
Arguments: ['allowed', 'plans']
12:26:55 - MESSAGE:
{'msg': '',
'plans_allowed': {'adaptive_scan': '{...}',
                  'count': '{...}',
                  'count_bundle_test': '{...}',
                  'fly': '{...}',
                  ...},
'plans_allowed_uid': 'ad1963db-d561-441d-a4c3-f94ee2780a61',
'success': True}

Upload script to RE Manager:

$ qserver script upload test_script.py
Arguments: ['script', 'upload', 'test_script.py']
12:29:59 - MESSAGE:
{'msg': '', 'success': True, 'task_uid': '1234adbc-b181-4003-b5fb-9d72ab0f7fc2'}

While the script is running, check RE Manager status. The manager and environment status is now returned as 'executing_task':

$ qserver status
Arguments: ['status']
12:30:04 - MESSAGE:
{ ...
'manager_state': 'executing_task',
  ...
'worker_environment_state': 'executing_task'}

Make sure that the task is running by checking the task status (using task UID):

$ qserver task status '1234adbc-b181-4003-b5fb-9d72ab0f7fc2'
Arguments: ['task', 'status', '1234adbc-b181-4003-b5fb-9d72ab0f7fc2']
12:30:16 - MESSAGE:
{'msg': '',
'status': 'running',
'success': True,
'task_uid': '1234adbc-b181-4003-b5fb-9d72ab0f7fc2'}

Wait until script execution is finished. Check the status again:

$ qserver task status '1234adbc-b181-4003-b5fb-9d72ab0f7fc2'
Arguments: ['task', 'status', '1234adbc-b181-4003-b5fb-9d72ab0f7fc2']
12:30:43 - MESSAGE:
{'msg': '',
'status': 'completed',
'success': True,
'task_uid': '1234adbc-b181-4003-b5fb-9d72ab0f7fc2'}

View the results of task execution. The return value is always None, because the script does not return any value. Check that success is True and error message and traceback are empty strings:

$ qserver task result '1234adbc-b181-4003-b5fb-9d72ab0f7fc2'
Arguments: ['task', 'result', '1234adbc-b181-4003-b5fb-9d72ab0f7fc2']
12:30:54 - MESSAGE:
{'msg': '',
'result': {'msg': '',
            'return_value': None,
            'success': True,
            'task_uid': '1234adbc-b181-4003-b5fb-9d72ab0f7fc2',
            'time_start': 1659544199.5750947,
            'time_stop': 1659544229.7251163,
            'traceback': ''},
'status': 'completed',
'success': True,
'task_uid': '1234adbc-b181-4003-b5fb-9d72ab0f7fc2'}

Load the list of allowed plans and verify that count_test is in the list:

$ qserver allowed plans
Arguments: ['allowed', 'plans']
12:40:18 - MESSAGE:
{'msg': '',
'plans_allowed': {'adaptive_scan': '{...}',
                  'count': '{...}',
                  'count_bundle_test': '{...}',
                  'count_test': '{...}',
                  'fly': '{...}',
                  ...},
'plans_allowed_uid': 'e3a1a276-f081-450a-87d7-e17101e83deb',
'success': True}

Now the plan count_test can be placed in the queue and executed by RE Manager.

API used in this tutorial: ‘script_upload’, ‘status’, ‘plans_allowed’, ‘task_status’, ‘task_result’.

Queue Autostart Mode

In autostart mode, the execution of the queue is started automatically if the queue is not empty and the state of the manager and the worker allows to execute plans. See Queue Autostart Mode for more information.

Start RE Manager using instructions given in Starting the Queue Server.

Part I

Make sure that the queue and the history are empty and the autostart mode is disabled:

$ qserver status
{ ...
'items_in_queue': 0,
'items_in_history': 0,
...
'queue_autostart_enabled': False,
... }

Clear the queue and the history if necessary:

$ qserver queue clear
$ qserver history clear

Open the environment:

$ qserver environment open

Enable the autostart mode:

$ qserver queue autostart enable

Check the autostart mode is enabled:

$ qserver status
{ ...
'queue_autostart_enabled': True,
... }

Add a plan to the queue:

$ qserver queue add plan '{"name": "count", "args": [["det1", "det2"]], "kwargs": {"num": 10, "delay": 1}}'

Observe that the execution of the plan starts automatically. Check the status to make sure that the executed plan was added to the plan history and the autostart mode is still on:

$ qserver status
{ ...
'items_in_queue': 0,
'items_in_history': 1,
...
'queue_autostart_enabled': True,
... }

Close the environment:

$ qserver environment close

Part II

Autostart mode is automatically disabled whenever the queue is stopped (using ‘queue_stop’ API or queue_stop instruction), currently running plan is stopped aborted or halted or current plan fails (unless ignore_failures queue mode is enabled, see ‘queue_mode_set’ API).

Verify that the queue is still in autostart mode:

$ qserver status
{ ...
'queue_autostart_enabled': True,
... }

The environment is still closed. Add two plans to the queue:

$ qserver queue add plan '{"name": "count", "args": [["det1", "det2"]], "kwargs": {"num": 10, "delay": 1}}'
$ qserver queue add plan '{"name": "count", "args": [["det1", "det2"]], "kwargs": {"num": 10, "delay": 1}}'

Now open the environment:

$ qserver environment open

Observe that the manager automatically starts the queue. Send requests to pause and then stop the plan while the first plan is still running:

$ qserver re pause
$ qserver re stop

Observe the RE Manager console output to verify that the plan stops. Now check the status to make sure that one plan remains in the queue and autostart mode is disabled:

$ qserver status
{ ...
'items_in_queue': 1,
'items_in_history': 2,
...
'queue_autostart_enabled': False,
... }

API used in this tutorial: ‘queue_autostart’, ‘status’, ‘environment_open’, ‘environment_close’, ‘re_pause’, ‘re_resume’, ‘re_stop’, ‘re_abort’, ‘re_halt’.

Start Queue Server in IPython Mode

Queue Server may be configured to run the worker environment in IPython kernel. In this mode, the worker accepts startup code and uploaded scripts that contain IPython-specific features, such as magics, user_ns, etc. Users may also connect to the kernel directly using Jupyter Console and run plans and execute IPython commands interactively. This allows to implement dual workflows, that include API-based control of execution of typical plans (e.g. to support user-friendly GUI) and interactive IPython access to the environment for expert use.

See more information on IPython mode in Running RE Worker with IPython Kernel.

RE Manager can be started in IPython mode using the parameter --use-ipython-kernel=ON:

$ start-re-manager --use-ipython-kernel=ON

By default, IPython kernel is using agg Matplotlib backend, which prevents Matplotlib plots from being displayed. To enable plotting, pass a different backend using --ipython-matplotlib parameter:

$ start-re-manager --use-ipython-kernel=ON --ipython-matplotlib=qt5

Now start RE Manager (with or without setting Matplotlib backend), then open the environment:

$ qserver environment open

The console output of RE Manager will contain the following:

[I 2023-04-30 17:31:45,811 bluesky_queueserver.manager.worker] Initializing IPython kernel ...
NOTE: When using the `ipython kernel` entry point, Ctrl-C will not work.

To exit, you will have to explicitly quit this process, by either sending
"quit" from a client, or using Ctrl-\ in UNIX-like environments.

To read more about this, see https://github.com/ipython/ipython/issues/2049

To connect another client to this kernel, use:
    --existing kernel-824988.json
Loading file '/tmp/qserver/ipython/profile_collection_sim/startup/00-ophyd.py'
Loading file '/tmp/qserver/ipython/profile_collection_sim/startup/15-plans.py'
Loading file '/tmp/qserver/ipython/profile_collection_sim/startup/99-custom.py'
[I 2023-04-30 17:31:46,817 bluesky_queueserver.manager.worker] IPython kernel connection info:
{'transport': 'tcp',
'ip': '127.0.0.1',
'shell_port': 45493,
'iopub_port': 34885,
'stdin_port': 43199,
'hb_port': 39801,
'control_port': 57395,
'signature_scheme': 'hmac-sha256',
'key': '070a4a0d-a4e8199193269ca4f2785595'}

Check RE Manager status:

$ qserver status
{ ...
'ip_kernel_state': 'idle',
'ip_kernel_captured': False,
... }

The kernel is in the 'idle' state and ready to execute tasks, the parameter ip_kernel_captured indicates if the kernel is executing a foreground task (plan, function or script) started by RE Manager.

Add a plan to the queue and start the queue:

$ qserver queue add plan '{"name": "count", "args": [["det1", "det2"]], "kwargs": {"num": 10, "delay": 1}}'
$ qserver queue start

Check the status while the plan is running. The kernel state is now busy and the kernel is ‘captured’ by RE Manager:

$ qserver status
{ ...
'ip_kernel_state': 'busy',
'ip_kernel_captured': True,
... }

Now open another terminal and connect to the kernel using Jupyter Console. The qserver-console CLI tool downloads current kernel connection info from RE Manager and passes it to the Jupyter Console:

$ qserver-console

Alternatively, qserver-qtconsole may be used to start Jupyter Qt Console:

$ qserver-qtconsole

Start a plan in the IPython prompt:

In [1]: RE(count([det1, det2], num=10, delay=1))

As the plan is executed in Jupyter Console, the output is also displayed in RE Manager console output. Jupyter Console may be closed while the plan is running (Ctrl-C then Ctrl-D) but the plan will continue to run in the kernel. Check the status while the plan is running:

$ qserver status
{ ...
'ip_kernel_state': 'busy',
'ip_kernel_captured': False,
... }

The kernel state is busy, but it is not ‘captured’ by the manager. RE Manager can not start execution of a foreground task (plan/function/script) until the task started from the console is completed and the kernel is idle.

Note

Use Ctrl-D to exit Jupyter Console: typing quit or exit will close the kernel and, respectively, the worker environment.

Close the environment:

$ qserver environment close

The environment can be closed only if the kernel is idle. The operation of destroying the environment will kill the worker process and, respectively, the kernel independent of its state.

API used in this tutorial: ‘status’, ‘queue_item_add’, ‘queue_start’, ‘environment_open’, ‘environment_close’.

IPython Mode: Interrupting Kernel

At this moment there is no way to send interrupts to kernel from Jupyter Console (by pressing Ctrl-C). This tutorial demonstrates how to pause a plan using ‘re_pause’ API or interrupt a task using ‘kernel_interrupt’ API. While a plan can be paused by sending ‘kernel_interrupt’ API request once or twice, it is recommended that ‘re_pause’ API is used.

The tutorial illustrates how to initiate the interrupts using command line, which is impractical in production deployments. It is assumed that the API will accessed via GUI components for convenience.

Start RE Manager in IPython mode:

$ start-re-manager --use-ipython-kernel=ON --ipython-matplotlib=qt5

Open a separate terminal for executing qserver commands and open the environment:

$ qserver environment open

Start Jupyter Console in a separate terminal:

$ qserver-console

A. Pausing a plan

Start a plan in a console:

In [1]: RE(count([det1, det2], num=20, delay=1))

While the plan is running in the console, pause the plan using ‘re_pause’ API:

$ qserver re pause

Observe the plan output in the Jupyter Console to make sure the plan is paused. Check the status:

$ qserver status
{ ...
'items_in_queue': 0,
'items_in_history': 0,
'running_item_uid': None,
'manager_state': 'idle',
...
'worker_environment_exists': True,
'worker_environment_state': 'idle',
're_state': 'paused',
'ip_kernel_state': 'idle',
'ip_kernel_captured': False,
... }

Note, that both RE Manager and Worker Environment states are ‘idle’, so RE Manager is not aware of the paused plan. Now resume the plan in Jupyter console and let it run to completion:

In [2]: RE.resume()

B. Sending Interrupt (Ctrl-C)

In Jupyter Console start a long running task:

In [3]: for n in range(30):
   ...:     print(f"n = {n}")
   ...:     ttime.sleep(1)

Send ‘kernel_interrupt’ API request from the terminal:

$ qserver kernel interrupt

Observe the output in Jupyter Console to make sure the task was interrupted:

In [5]: for n in range(30):
  ...:     print(f"n = {n}")
  ...:     ttime.sleep(1)
  ...:
n = 0
n = 1
n = 2
n = 3
n = 4
n = 5
n = 6
n = 7
n = 8
---------------------------------------------------------------------------
KeyboardInterrupt                         Traceback (most recent call last)
Cell In[5], line 3
      1 for n in range(30):
      2     print(f"n = {n}")
----> 3     ttime.sleep(1)

Close the environment:

$ qserver environment close

API used in this tutorial: ‘status’, ‘environment_open’, ‘re_pause’, ‘kernel_interrupt’, ‘environment_close’.

IPython Mode: Updating Environment

Users may use direct connection to IPython kernel via Jupyter Console to add, remove or modify plans and devices in the worker namespace. RE Manager is not immediately aware of changes to the namespace. To make new or modified plans or devices visible client applications, the lists of existing/allowed plans and devices can be updated using ‘environment_update’ API.

Start RE Manager in IPython mode:

$ start-re-manager --use-ipython-kernel=ON --ipython-matplotlib=qt5

Open a separate terminal for executing qserver commands and open the environment:

$ qserver environment open

Start Jupyter Console in a separate terminal:

$ qserver-console

In Jupyter Console add a new (trivial) plan:

In [6]: def my_plan():
  ...:     yield from bps.sleep(1)

In the terminal check the list of allowed plans. The plan my_plan is not in the list:

$ qserver allowed plans
{'success': True,
'msg': '',
'plans_allowed': {'adaptive_scan': '{...}',
                  'count': '{...}',
                  'count_bundle_test': '{...}',
                  'fly': '{...}',
                  'grid_scan': '{...}',
                  'inner_product_scan': '{...}',
                  'list_grid_scan': '{...}',
                  'list_scan': '{...}',
                  'log_scan': '{...}',
                  'marked_up_count': '{...}',
                  'move_then_count': '{...}',
                  'plan_test_progress_bars': '{...}',
                  'ramp_plan': '{...}',
                  'rel_adaptive_scan': '{...}',
                  'rel_grid_scan': '{...}',
                  'rel_list_grid_scan': '{...}',
                  'rel_list_scan': '{...}',
                  'rel_log_scan': '{...}',
                  'rel_scan': '{...}',
                  'rel_spiral': '{...}',
                  'rel_spiral_fermat': '{...}',
                  'rel_spiral_square': '{...}',
                  'relative_inner_product_scan': '{...}',
                  'scan': '{...}',
                  'scan_nd': '{...}',
                  'sim_multirun_plan_nested': '{...}',
                  'spiral': '{...}',
                  'spiral_fermat': '{...}',
                  'spiral_square': '{...}',
                  'tune_centroid': '{...}',
                  'tweak': '{...}',
                  'x2x_scan': '{...}'},
'plans_allowed_uid': '7eb1e35c-08a2-418e-9a99-2ae1a3fb99f6'}

Now update the environment by sending ‘environment_update’ API request:

$ qserver environment update

and check the list of allowed plans:

$ qserver allowed plans
{'success': True,
'msg': '',
'plans_allowed': {'scan': '{...}',
                  'relative_inner_product_scan': '{...}',
                  'move_then_count': '{...}',
                  'spiral_square': '{...}',
                  'rel_log_scan': '{...}',
                  'inner_product_scan': '{...}',
                  'count': '{...}',
                  'log_scan': '{...}',
                  'spiral': '{...}',
                  'adaptive_scan': '{...}',
                  'fly': '{...}',
                  'ramp_plan': '{...}',
                  'sim_multirun_plan_nested': '{...}',
                  'spiral_fermat': '{...}',
                  'list_scan': '{...}',
                  'rel_list_grid_scan': '{...}',
                  'plan_test_progress_bars': '{...}',
                  'rel_adaptive_scan': '{...}',
                  'grid_scan': '{...}',
                  'my_plan': '{...}',
                  'marked_up_count': '{...}',
                  'rel_spiral': '{...}',
                  'x2x_scan': '{...}',
                  'rel_grid_scan': '{...}',
                  'rel_list_scan': '{...}',
                  'tweak': '{...}',
                  'tune_centroid': '{...}',
                  'rel_spiral_square': '{...}',
                  'rel_spiral_fermat': '{...}',
                  'rel_scan': '{...}',
                  'scan_nd': '{...}',
                  'count_bundle_test': '{...}',
                  'list_grid_scan': '{...}'},
'plans_allowed_uid': '3f4b9289-379c-4d6c-a230-f6db8c08712e'}

Note that the plans_allowed_uid changed and the my_plan plan is in the list now.

Close the environment:

$ qserver environment close

API used in this tutorial: ‘environment_open’, ‘plans_allowed’, ‘environment_update’, ‘environment_close’.

Locking RE Manager

RE Manager can be temporarily locked by a user using a ‘secret’ key. The user is expected to remember (or keep) the key and unlock the manager when safe. The user may choose to lock the worker environment and/or the queue which prevents other users to change the state of environment (start the queue, run plans, upload scripts etc.) or the queue (add, edit or reorder plans in the queue etc.) unless they are provided with the key. For more detailed description see Locking RE Manager.

Start RE Manager using instructions given in Starting the Queue Server.

Check the status of RE Manager:

$ qserver status
Arguments: ['status']
08:40:21 - MESSAGE:
{ ...
'lock': {'environment': False, 'queue': False},
'lock_info_uid': '5a992925-3c86-420f-b338-576eeb8778d3',
... }

The lock parameter indicates if the environment and the queue are locked, lock_info_uid is updated each time the lock status is changed and intended for use by monitoring client applications.

Load the lock status:

$ qserver lock info
Arguments: ['lock', 'info']
12:02:41 - MESSAGE:
{'lock_info': {'emergency_lock_key_is_set': False,
              'environment': False,
              'note': None,
              'queue': False,
              'time': None,
              'time_str': '',
              'user': None},
'lock_info_uid': '5a992925-3c86-420f-b338-576eeb8778d3',
'msg': '',
'success': True}

When the manager is locked, the status includes the name of the user (user) who applied the lock, time (time, time_str) when the lock was applied and optional note (note) for other users of the system, explaining the reason why the lock was applied. The parameter emergency_lock_key_is_set (False) indicates that the emergency key is not set and the manager can be unlocked only only with the key used to lock it.

Lock the environment with a note:

$ qserver --lock-key userlockkey lock environment "The environment is locked. Do not unlock environment!"
Arguments: ['lock', 'environment', 'The environment is locked. Do not unlock environment!']
12:03:40 - MESSAGE:
{'lock_info': {'emergency_lock_key_is_set': False,
              'environment': True,
              'note': 'The environment is locked. Do not unlock environment!',
              'queue': False,
              'time': 1658765020.0658383,
              'time_str': '07/25/2022 12:03:40',
              'user': 'qserver-cli'},
'lock_info_uid': '05c2127b-5569-411a-8212-debf7149390b',
'msg': '',
'success': True}

The lock key can be aribtrarily selected by the user who locks the manager (in this example the key is userlockkey). The parameters user, time, time_str and note are properly set now and the parameter environment is True.

qserver lock info may be used to validate the lock key. The call always succeeds if called without the lock key. If the manager is locked, then the included key is validated and the call succeeds only if the key is valid. Try validating an invalid key:

$ qserver --lock-key someinvalidkey lock info
Arguments: ['lock', 'info']
12:04:14 - MESSAGE:
{'lock_info': {'emergency_lock_key_is_set': False,
              'environment': True,
              'note': 'The environment is locked. Do not unlock environment!',
              'queue': False,
              'time': 1658765020.0658383,
              'time_str': '07/25/2022 12:03:40',
              'user': 'qserver-cli'},
'lock_info_uid': '05c2127b-5569-411a-8212-debf7149390b',
'msg': 'Error: Invalid lock key: \n'
        'RE Manager is locked by qserver-cli at 07/25/2022 12:03:40\n'
        'Environment is locked: True\n'
        'Queue is locked:       False\n'
        'Emergency lock key:    not set\n'
        'Note: The environment is locked. Do not unlock environment!',
'success': False}

The call fails ('success': False) and the error message indicates that the lock key is invalid. Try validating the valid key:

$ qserver --lock-key userlockkey lock info
Arguments: ['lock', 'info']
12:04:41 - MESSAGE:
{'lock_info': {'emergency_lock_key_is_set': False,
              'environment': True,
              'note': 'The environment is locked. Do not unlock environment!',
              'queue': False,
              'time': 1658765020.0658383,
              'time_str': '07/25/2022 12:03:40',
              'user': 'qserver-cli'},
'lock_info_uid': '05c2127b-5569-411a-8212-debf7149390b',
'msg': '',
'success': True}

Since the environment is locked, all operations that change the state of environment, such as opening and closing the environment, starting the queue etc., can be executed only if a valid lock key is included in the call. Try opening the environment without the lock key:

$ qserver environment open
Arguments: ['environment', 'open']
12:05:14 - MESSAGE:
{'msg': 'Error: Invalid lock key: \n'
        'RE Manager is locked by qserver-cli at 07/25/2022 12:03:40\n'
        'Environment is locked: True\n'
        'Queue is locked:       False\n'
        'Emergency lock key:    not set\n'
        'Note: The environment is locked. Do not unlock environment!',
'success': False}

Now try opening the environment with the lock key:

$ qserver --lock-key userlockkey environment open
Arguments: ['environment', 'open']
12:05:44 - MESSAGE:
{'msg': '', 'success': True}

The operation succeeded. Now close the environment with the lock key:

$ qserver --lock-key userlockkey environment close
Arguments: ['environment', 'close']
12:06:09 - MESSAGE:
{'msg': '', 'success': True}

qserver lock also allows to lock the queue (blocks access to queue operations) or both the environment and the queue. Try to lock the queue (optionally add the note):

$ qserver --lock-key userlockkey lock queue
Arguments: ['lock', 'queue']
12:06:34 - MESSAGE:
{'lock_info': {'emergency_lock_key_is_set': False,
              'environment': False,
              'note': None,
              'queue': True,
              'time': 1658765194.4385393,
              'time_str': '07/25/2022 12:06:34',
              'user': 'qserver-cli'},
'lock_info_uid': '6af981eb-0690-4110-839f-8e315649ef40',
'msg': '',
'success': True}

and add plans to the queue with and without the --lock-key parameter, then lock the environment and the queue:

$ qserver --lock-key userlockkey lock all
Arguments: ['lock', 'all']
12:06:55 - MESSAGE:
{'lock_info': {'emergency_lock_key_is_set': False,
              'environment': True,
              'note': None,
              'queue': True,
              'time': 1658765215.8313878,
              'time_str': '07/25/2022 12:06:55',
              'user': 'qserver-cli'},
'lock_info_uid': 'bd84f374-8b05-46d8-bbd9-e61a0c599b15',
'msg': '',
'success': True}

The lock may be applied repeatedly to the locked manager to change the lock options as long as the valid lock key is passed. The lock key can not be changed without unlocking the manager.

To unlock the manager run qserver unlock with the valid lock key:

$ qserver --lock-key userlockkey unlock
Arguments: ['unlock']
12:07:24 - MESSAGE:
{'lock_info': {'emergency_lock_key_is_set': False,
              'environment': False,
              'note': None,
              'queue': False,
              'time': None,
              'time_str': '',
              'user': None},
'lock_info_uid': '6d3e834d-eccd-44be-87b1-db3b8557bfcb',
'msg': '',
'success': True}

The lock status is stored in Redis and persists between sessions, i.e. restarting RE Manager does not clear the lock. If the key is lost, then the manager can be unlocked using an optional emergency lock key:

# Start RE Manager with the emergency lock key
QSERVER_EMERGENCY_LOCK_KEY_FOR_SERVER=emlockkey start-re-manager

# Lock the environment
$ qserver --lock-key key-to-forget lock environment

# Assume that the key is lost. Unlock the manager with the emergency key.
$ qserver --lock-key emlockkey unlock

# Check lock status. The manager should be unlocked.
$ qserver lock info

If the emergency key is not set, then the lock can be cleared by running qserver-clear-lock CLI tool and then restarting RE Manager service or application. The tool requires access to Redis server used by RE Manager. The following steps illustrate the procedure:

# Start RE Manager.

# Lock the environment
$ qserver --lock-key key-to-forget lock environment

# Check lock status
$ qserver lock info

# Assume that the key is lost. Clear the lock in Redis. Pass '--redis-addr' if needed.
qserver-clear-lock

# Stop and restart RE Manager application.

# Check lock status. The manager should be unlocked.
$ qserver lock info

API used in this tutorial: ‘lock’, ‘lock_info’, ‘unlock’, ‘status’, ‘environment_open’, ‘environment_close’.

Changing User Group Permissions

RE manager provides ‘permissions_get’ and ‘permissions_set’ API that allow clients to download and upload user group permissions. The Python API operate with permissions represented as a dictionary, which could be downloaded, changed and then uploaded to the manager. Once the dictionary with permissions are uploaded, the lists of allowed plans and devices are updated by the manager to reflect new permissions. The qserver CLI implementation of the API are not as flexible: qserver permissions get loads and prints the dictionary of permissions and qserver permissions set is accepting the path to YAML file that contains new user group permissions, so a simple load-change-upload operation currently can not be performed easily via CLI.

Start RE Manager using instructions given in Starting the Queue Server.

Load and check the list of allowed plans. Make sure that grid_scan is in the list:

$ qserver allowed plans
Arguments: ['allowed', 'plans']
13:43:41 - MESSAGE:
{'msg': '',
'plans_allowed': { ...
                  'fly': '{...}'
                  'grid_scan': '{...}',
                  'inner_product_scan': '{...}',
                  ... },
'plans_allowed_uid': '68753b90-7716-4ce6-b273-b2b8e3646123',
'success': True}

Load and inspect permissions for the primary user group: users are allowed to execute all plans (see Configuring User Group Permissions):

$ qserver permissions get
Arguments: ['permissions', 'get']
13:47:20 - MESSAGE:
{'msg': '',
'success': True,
'user_group_permissions': {'user_groups': {'primary': {'allowed_devices': [':?.*:depth=5'],
                                                      'allowed_functions': ['function_sleep'],
                                                      'allowed_plans': [':.*'],
                                                      'forbidden_devices': [None],
                                                      'forbidden_plans': [None]},
                                            ...
                                          }
                          }

Let’s create a YAML file (e.g. ‘new_permissions.yaml’) with modified user group permissions, which forbid users from adding grid_scan plan to the queue (root user group, which defines permissions that are applied to all other groups, must always exist in the dictionary of permissions):

user_groups:
  root:  # The group includes all available plan and devices
    allowed_plans:
      - null  # Allow all
    forbidden_plans:
      - ":^_"  # All plans with names starting with '_'
    allowed_devices:
      - null  # Allow all
    forbidden_devices:
      - ":^_:?.*"  # All devices with names starting with '_'
    allowed_functions:
      - null  # Allow all
    forbidden_functions:
      - ":^_"  # All functions with names starting with '_'
  primary:  # The group includes beamline staff, includes all or most of the plans and devices
    allowed_plans:
      - ":.*"  # Different way to allow all plans.
    forbidden_plans:
      - "grid_scan"
    allowed_devices:
      - ":?.*:depth=5"  # Allow all device and subdevices. Maximum deepth for subdevices is 5.
    forbidden_devices:
      - null  # Nothing is forbidden
    allowed_functions:
      - "function_sleep"  # Explicitly listed name

Upload permissions to RE Manager:

$ qserver permissions set new_permissions.yaml
Arguments: ['permissions', 'set', 'new_permissions.yaml']
13:57:31 - MESSAGE:
{'msg': '', 'success': True}

Load the permission again to verify that they are modified:

$ qserver permissions get
Arguments: ['permissions', 'get']
13:57:40 - MESSAGE:
{'msg': '',
'success': True,
'user_group_permissions': {'user_groups': {'primary': {'allowed_devices': [':?.*:depth=5'],
                                                      'allowed_functions': ['function_sleep'],
                                                      'allowed_plans': [':.*'],
                                                      'forbidden_devices': [None],
                                                      'forbidden_plans': ['grid_scan']},
                                            'root': {'allowed_devices': [None],
                                                    'allowed_functions': [None],
                                                    'allowed_plans': [None],
                                                    'forbidden_devices': [':^_:?.*'],
                                                    'forbidden_functions': [':^_'],
                                                    'forbidden_plans': [':^_']}}}}

Check that grid_scan is not in the updated list of allowed plans:

$ qserver allowed plans
Arguments: ['allowed', 'plans']
13:58:59 - MESSAGE:
{'msg': '',
'plans_allowed': { ...
                  'fly': '{...}',
                  'inner_product_scan': '{...}',
                   ... },
'plans_allowed_uid': '2c983eec-a7cb-4fd2-bc9a-ad4503c3bf9e',
'success': True}

API used in this tutorial: ‘permissions_get’, ‘permissions_set’, ‘plans_allowed’.

Running RE Manager with Custom Startup Code

All the tutorials in this section are using a set of built-in startup scripts that provide simulated devices and simple plans, which are sufficient to explore functionality of the Queue Server. Any practical application would require starting the server with custom startup scripts with Ophyd devices that represent real hardware and Bluesky plans that perform useful measurements. This tutorial provides instructions for configuring the server to load custom IPython-style set of startup scripts.

Instead of creating new scripts, we will copy the existing startup files in custom directory and configure the server to load scripts from this directory. Those scripts could be then modified or replaced custom scripts.

Step 1. Create a directory for the startup files in a convenient location, e.g. ~/qs_startup. The directory should be readable and writable for the user running RE Manager.

Step 2. Copy startup scripts (only .py files) and user_group_permissions.yaml from the repository to ~/qs_startup. The file existing_plans_and_devices.yaml will be generated by RE Manager as part of the tutorial, so do not copy it. The directory should contain the following files:

$ ls
00-ophyd.py  15-plans.py  99-custom.py  user_group_permissions.yaml

Step 3. Start RE Manager by specifying the path to startup directory:

$ start-re-manager --startup-dir ~/qs_startup
[W 2022-02-17 18:43:10,262 bluesky_queueserver.manager.start_manager] The file with the list of allowed plans and devices ('/home/dgavrilov/qs_startup/existing_plans_and_devices.yaml') does not exist. The manager will be started with empty list. The list will be populated after RE worker environment is opened the first time.
[I 2022-02-17 18:43:10,263 bluesky_queueserver.manager.manager] Starting ZMQ server at 'tcp://*:60615'
[I 2022-02-17 18:43:10,263 bluesky_queueserver.manager.manager] ZMQ control channels: encryption disabled
[I 2022-02-17 18:43:10,266 bluesky_queueserver.manager.manager] Starting RE Manager process
[I 2022-02-17 18:43:10,284 bluesky_queueserver.manager.manager] Loading the lists of allowed plans and devices ...
[W 2022-02-17 18:43:10,284 bluesky_queueserver.manager.profile_ops] List of plans and devices is not loaded. File 'existing_plans_and_devices.yaml' does not exist.
[I 2022-02-17 18:43:10,285 bluesky_queueserver.manager.manager] Starting ZeroMQ server ...
[I 2022-02-17 18:43:10,285 bluesky_queueserver.manager.manager] ZeroMQ server is waiting on tcp://*:60615

Step 4. Open RE Worker environment:

$ qserver environment open

Step 5. Verify that existing_plans_and_devices.yaml file was generated:

$ ls
00-ophyd.py  15-plans.py  99-custom.py  existing_plans_and_devices.yaml  user_group_permissions.yaml

RE Manager is ready and plans may be submitted to the queue and executed. If plans or devices are added or modified, the currently open environment must be closed and opened again to reload the startup files and generate the new list of existing plans and devices.

In some configurations, it is convenient to place the startup files in the startup directory for one of IPython profiles, so that they could be loaded into IPython. At NSLS-II it is traditional to use the IPython profile named collection to run Bluesky software and standard location for startup files is ~/.ipython/profile_collection/startup. RE Manager may be configured to find the startup files by explicitly specifying the directory:

$ start-re-manager --startup-dir ~/.ipython/profile_collection/startup

or by specifying the name of the IPython profile:

$ start-re-manager --startup-profile collection

In addition to IPython-style sets of startup files, RE Manager may be configured to load the code from a Python script (by specifying path to script file) or from an installed Python module. The configuration instructions may be found in the section Using a Configuration Files. Note, that the code for loading IPython-style startup files performs patching to provide some compatibility with features of IPython. Patching was implemented mostly to simplify transition from IPython workflow used on beamlines. Startup scripts are assumed to be written for execution in pure Python environment and are not patched. Ideally all blocks of code that use IPython features should be disabled when executed in by the Queue Server (see Detecting if the Code is Executed by RE Worker).

Manually Generating Lists of Existing Plans and Devices

RE Manager generates or updates the list of existing plans and devices automatically when RE Worker environment is opened, but in some cases it is convenient to generate the list manually. For example, the developers wishing to update existing_plans_and_devices.yaml in the ‘profile_collection_sim’ directory when the respective startup files are modified have the only option to do it manually (RE Manager is designed not to automatically modify files in built-in profile_collection_sim directory).

Step 1. Create the directory with startup files and copy startup Python files as described in Running RE Manager with Custom Startup Code. We will assume that the files are in the directory ~/qt_startup. The directory should contain the following files:

$ ls
00-ophyd.py  15-plans.py  99-custom.py

Step 2. Use qserver-list-plans-devices CLI tool to generate existing_plans_and_devices.yaml:

$ qserver-list-plans-devices --startup-dir ~/qs_startup --file-dir ~/qs_startup

Step 3. Check if the file existing_plans_and_devices.yaml is created in the directory:

$ ls
00-ophyd.py  15-plans.py  99-custom.py  existing_plans_and_devices.yaml

Alternatively, qserver-list-plans-devices may be started from the ~/qs_startup directory:

$ cd ~/qs_startup
$ qserver-list-plans-devices --startup-dir .

Remote Monitoring of RE Manager Console Output

Queue Server provides a simple qserver-console-monitor CLI tool for remote monitoring of console output of RE Manager. The tool subscribes to messages published by RE Manager over 0MQ and displays text contents of the messages. The output of qserver-console-monitor is expected to be identical to the output of RE Manager. There is also an option to disable printing of console output RE Manager and use the external monitoring application for visualizing of RE Manager output.

In Terminal 1 start qserver-console-monitor:

$ qserver-console-monitor

In Terminal 2 start RE Manager with console output publishing available:

$ start-re-manager --zmq-publish-console ON

Use Terminal 3 to run some commands using qserver tool. Terminals 1 and 2 must display identical output. Multiple instances of qserver-console-monitor may be running simultaneously and display the same console output. Experiment with closing (Ctrl-C) and restarting qserver-console-monitor. Notice that all published console output is lost while the monitor is closed.

In Terminal 2, close RE Manager (Ctrl C) and restart it with the option that disables printing of the console output:

$ start-re-manager --zmq-publish-console ON --console-output OFF

Notice that no output is printed in Terminal2. External monitor (running in Terminal 1) is needed to visualize the output from RE Manager.

In practice, the client applications are expected to implement the functionality for subscribing to published RE Manager output and displaying it to users. The use of qserver-console-monitor tool should be limited to evaluation, testing and debugging of the systems using RE Manager.