Software Overview  |  Sitemap  |  Downloads  |  Developers  |  Forums
Small_clear_logo_llc

Creating Asynchronous Virtual Devices

Sometimes Virtual Devices need significant time to respond to an input event. Other times, Virtual Devices need to generate events in response to something other than input events. When Virtual Devices have any of these requirements, we create an asynchronous Virtual Device.

In the Creating Virtual Devices section, we created a synchronous Virtual Device which executed system commands. One of its faults was that if its command blocked, the system would block. As an example, if you ran a "sleep 2" command (supported on *nix systems), the command would block for 2 seconds before returning, suspending all other events in the system for those 2 seconds. We are going to turn the command_runner Virtual Device into an asynchronous Virtual Device.

The "generate_event" Method

If you wish to generate an event from within a Virtual Device, you call the generate_event method. The method takes a single hash parameter, and the hash contains output events. The hash is the same type of hash as the Virtual Device input hash and return value hash, with the keys being terminal names, and values being event values. To see a a full definition of these hashes, see the Virtual Device Programs section.

To create an event for the "out" terminal with a value of "hello", one would write:

generate_event("out"=>"hello")

Making the "command_runner" Virtual Device Asynchronous

We are going the alter the command_runner program, so it always returns quickly, fixing its blocking problem. To fix it, we'll create a new thread to execute the system command, and return from the system call immediately. We'll let our new thread create an event when the command is done.

# Version which won't block the event thread
module VdCommandRunner

  private

  ##################################################################
  # Define so whatever is on the "in" pin is run as a command and
  # result is output on the "out" pin.
  ##################################################################
  def respond_to_inputs(inputs, outputs = {})
    if (command = inputs["in"]).kind_of?(String) &&
        (command =~ /^\"(.*)\"/)
      command = $1
      # run command on new thread, so we don't block system thread
      Thread.new do
        result = '"' + `#{command}` + '"' # quote return value
        generate_event("out" => result)
      end
    end
    outputs # return (empty) result - thread will return result
  end

  ##################################################################
  # Define our terminals
  ##################################################################
  def terminal_dids
    %W[in out]
  end

end # module

That fixes the blocking problem. When you try this program out, you may need to refresh your view in order to see your result. The thread we've created sometimes finishes after the command is 'done'.

Virtual Device Errors

So far, we've seen 2 actions a Virtual Device can take - returning events and returning no events. Generally, that's about all you want a system Device to do. However, what if there is a problem which you want to let the user or you, the developer, know about?

If you want to signal an error, you can raise a RuntimeError. Anytime the system encounters a RuntimeError, it will trap the exception and write its value to the Log. So, if we wanted to let the user know about a Device problem, we could raise a RuntimeError exception. If you choose to raise a RuntimeError exception, make sure you will only do them rarely. Flooding the Log with errors may mask Log entries of interest from other parts of the system.

Adding Error Logging to "command_runner"

The last command_runner implementation won't block, but it can run commands on top of each other (you can run another command before the previous one is finished). Perhaps you don't want that. You could check to see if a command is done before starting the next. You could also let the user to know about the problem. We'll log an error to tell the user that. We'll also log an error if the command is not formatted correctly.

# Version which won't block the event thread and which logs errors
module VdCommandRunner

  private

  ###########################################################################
  # Define so whatever is on the "in" pin is run as a command and
  # result is output on the "out" pin.
  ###########################################################################
  def respond_to_inputs(inputs, outputs = {})
    if (command = inputs["in"]).kind_of?(String) &&
        (command =~ /^\"(.*)\"/)
      command = $1

      # check to see if a command is in process
      if @command_thread && @command_thread.alive?
        raise "Error, Command Runner busy.  Not running command: #{command}"
      end

      # run command on new thread, so we don't block system thread
      @command_thread = Thread.new do
        result = '"' + `#{command}` + '"' # quote return value
        generate_event("out" => result)
      end
    else
      raise "Error: command must be a double quoted string (#{command})"
    end
    outputs # return (empty) result - thread will return result
  end

  ###########################################################################
  # Define our terminals
  ###########################################################################
  def terminal_dids
    %W[in out]
  end

end # module

Try running this Virtual Device with an unquoted command. Try running "sleep 20" and then "uptime" before the 20 seconds are up. Check the Log for errors.

Catalina Computing, LLC.

Copyright © Catalina Computing, LLC. (2013-2018)




Page last updated: Thu Apr 3 02:20:48 2014 (UTC)