FAQ on Bridges/en
Inhaltsverzeichnis
- 1 Why Bridged Actions and not Script Actions?
- 2 Which Languages are Supported
- 3 How are Bridges Started
- 4 Who Initiates the Connection
- 5 Ports Used for Bridge Communication
- 6 What are Proxy Objects
- 7 What can I do if a Bridge is Dead(locked)
- 8 I got an Object (Reference) from a Bridge but Access to its Fields is Slow
- 9 I get Timeouts - how can I change the Defaults ?
- 10 I don't see the difference between DLL actions and bridged C-actions; when to use which?
- 11 How can I debug the code in the bridge
- 12 Security Considerations
- 13 [OS X Only] CrashReporter Dialog Boxes
Why Bridged Actions and not Script Actions?[Bearbeiten]
Script actions will start the language interpreter anew for every such action. Thus, no internal state can be held and transferred from one such action to another. Especially, it is not possible to open a communication channel on one script action, pass it back to expecco, and use it with different followup script actions (i.e. actions like "open-port" / "send message" / "receive message" / "close port" etc.).
For this, it is required to call individual API-entries inside the bridge code, and return handles from the bridge to expecco, which can later be passed to other API entries.
This of course only makes sense, if the interpreter stays active during the lifetime of the connection.
On the other hand, script actions are easier to use if a self contained program or app is to be started, or which performs a single processing action or which stays alive for longer, serving a socket or database. In any such case, there is no need to exchange individual data objects (such as handles) with expecco. It may however interact with expecco through other communication channels, such as sockets, RPC calls, SOAP interface or stdin/stdout text ports.
Thus, you will typically use script actions to start server applications and bridged actions to call individual functions of a framework.
Which Languages are Supported[Bearbeiten]
By the time this chapter was written, bridged code execution is supported for:
- Java (using either Groovy or Jython as "glue" or directly using proxy objects in expecco)
- Python (both Python 2.7 and Python 3.x)
- .NET (using IronPython as "glue" or directly via proxy objects)
- NodeJS (i.e. JavaScript)
- C/C++ (Root/Cling is being developed currently)
- Smalltalk (Smalltalk/X and VisualWorks are currently supported; VisualAge may come in the near future, if there is sufficient customer interest)
- Ruby
Plans are to support Dart and Scheme in the future.
How are Bridges Started[Bearbeiten]
Bridges on the local machine are usually automatically started by expecco itself, when the first bridged action is executed (and stays alive, until expecco is terminated, or bridge connections are closed via the menu or via an expecco action block). Some language interpreters may take a few seconds to come up, so the first such action will execute relatively slow compared to later actions. You can add a dummy (empty) bridged action to your startup actions (project's postLoad action) to prepare the bridged interpreter.
Bridges on other machines, or additional bridges on the local machine must be started by executing a bridge start action block or simply by executing a shell/batch action to call the corresponding language interpreter (python, node, etc.). On Windows, a powershell action can be used for remote execution (given that your Windows security settings allow that). On Unix, a shell action executin a remote shell / ssh command could be used.
Who Initiates the Connection[Bearbeiten]
Normally, expecco opens a port and tells the bridge (via command line argument) to which port to connect. Depending on the setup of your network and firewall, it may sometimes be required to reverse this, and let the bridge open a port and expecco connect to it.
For this, some bridges support command line arguments ("--server" and "--port") to specify this behavior. Details are found in the individual bridge documentation pages.
Ports Used for Bridge Communication[Bearbeiten]
Unless ports are configured in the expecco settings, the following default ports are used (bridges typically listen on or connect to this port, unless an explicit "--port" argument is given):
Python 8677 , 8678 (debug) Python2 8679 , 8680 (debug) Python3 8681 , 8682 (debug) Jython 8777 , 8778 (debug) IronPython 8787 , 8788 (debug) Node 8577 , 8578 (debug) Electron 8588 , 8589 (debug) CBridge 8855 (also for Root/Cling) Smalltalk 8877 VASmalltalk 8879 VWSmalltalk 8881 Dart 8885 Ruby 8977 Scheme 8599
Note: for some bridges, two connections are used, the debug port being used by the debugger to inspect data and to support breakpoints, single stepping etc.
Note: expecco will try successive alternative ports, if the default port happens to be already in use.
Debugging is not supported on all bridges (eg. the C-bridge does not).
What are Proxy Objects[Bearbeiten]
These are placeholder objects inside expecco, which will look like regular Smalltalk (or JavaScript) objects inside expecco, but will forward any call via the socket ower to the bridge partner.
Thus, they can be used transparently inside Smalltalk and JavaScript elementary actions, performing whatever is implemented in the remote side.
For example, given a Java bridge connection named "bridge", you can instantiate a remote object there from within your local JavaScript code with:
var remoteArrayListClass, myList; remoteArrayListClass = bridge.resolveType("java.util.ArrayList"); myList = new remoteArrayListClass(10); myList.add("hello"); ...
in a Smalltalk action, the code is similar:
|remoteArrayList myList| remoteArrayListClass := bridge resolveType:'java.util.ArrayList'. myList := remoteArrayListClass new:10. myList add:'hello'. ...
This works without any remote code injection and is typically a bit faster than via Groovy. However, the language syntax is the local expecco syntax, which might be inconvenient to hard-core Java programmers (and the same is true for proxies to other language's bridge objects).
Remote proxy objects are also returned by the "makeRef
" API as described in the API documentation, and when remote objects are inspected in the expecco data inspector window.
What can I do if a Bridge is Dead(locked)[Bearbeiten]
The code executed in the bridge may run into an endless loop (for example, if your elementary code contains a bug), or when it calls a function which does not return. As a consequence, the corresponding elementary action will not finish.
Try to stop or abort the execution via the corresponding toolbar function (in the expecco editor page). Even though the corresponding expecco action has now finished (i.e. expecco will no longer wait for the action's return value and status), a problem might be reported when expecco is about to call another action inside that bridge: the bridge will still be in its loop, and the bridge will therefore not respond and expecco will report that the bridge is no longer answering.
You now have to kill the bridge, and start a new one, with the undesired consequence of loosing any state inside the previous bridge incarnation. Especially, any connections in the previous bridge are now lost.
To kill the bridge, perform one of the following:
- click the "Terminate Bridge" toolbar button in the "Test/Demo" tab.
This will terminate that language's bridges - select "Extras" → "Debugging" → "Close all Bridge Connections"
This will terminate all running bridges (maybe this is not what you want) - select "Extras" → "Debugging" → "OS Process Monitor" to get a list of all OS processes which have been started by expecco, select the one to terminate and choose "Terminate" from the context menu.
- select and terminate the bridge process in the Task Manager (Windows) or find its processID with the "ps" command and terminate it via the "kill" command (Unix)
If the bridge was not originally started by expecco (eg. if it is a remote bridge, which was started automatically or by a remote command), you have to log into that remote machine and terminate the bridge there.
In a production system, you may add time limits to actions which might suffer from locked bridges, and add a bridge-terminate action (found in the Standard Library) to be triggered via the exception pin of the time-controlled action.
I got an Object (Reference) from a Bridge but Access to its Fields is Slow[Bearbeiten]
Be reminded (see above), that when an object (reference) is returned from a bridge to expecco, a proxy (placeholder object) is created inside expecco, which is what appears at the output pin. This proxy object will intercept any calls to it and forward them to the real object inside the bridge, then wait for a response (the return value) and encode that again as a proxy.
Thus, every such operation involves a round trip which is typically in the order of milliseconds (if the bridge is on a remote machine, and additional routers/switches are involved, the round trip times may even be in the 10-millisecond range). These times can easily add up to seconds, for example if you enumerate a remote collection with many elements.
Possible solutions to that problem:
- install the code which enumerates the object inside the bridge (i.e. define another elementary action or require/import a piece of code which contains this functionality. Then call this code which runs completely inside the bridge.
- pass the object's encoding (for example as JSON) and decode it on the expecco side for the enumeration. This one call will then probably run much slower, but further processing (enumeration of elements) is much faster. This is the way to go, if bulk data has been collected inside the bridge, which has to be analyzed inside expecco afterwards.
Be aware though, that the object inside expecco is effectively a "copy" of the object; if the object will change inside the bridge, expecco will still present the old state.
I get Timeouts - how can I change the Defaults ?[Bearbeiten]
There is currently no dialog to change the default settings (except for the CBridge in v23.1).
However, the default values which control the timeouts can be changed via Smalltalk expressions (or corresponding JavaScript expressions).
To set a common default for all Python, CBridge and NodeJS bridge
In Smalltalk syntax:
SimpleBridge connectTimeout: seconds. SimpleBridge defineAckTimeoutMS: millis. SimpleBridge executeAckTimeoutMS: millis.
or in JavaScript syntax:
SimpleBridge.connectTimeout(seconds); SimpleBridge.defineAckTimeoutMS(millis); SimpleBridge.executeAckTimeoutMS(millis);
We are sorry for the inconsistent argument (seconds vs. milliseconds); these result from history and later added features. We will provide a consistent interface in later versions.
To set the default for individual language bridges:
PythonBridge connectTimeout: seconds. PythonBridge defineAckTimeoutMS: millis. PythonBridge executeAckTimeoutMS: millis. CBridge connectTimeout: seconds. CBridge defineAckTimeoutMS: millis. CBridge executeAckTimeoutMS: millis. NodeJSBridge connectTimeout: seconds. NodeJSBridge defineAckTimeoutMS: millis. NodeJSBridge executeAckTimeoutMS: millis.
You can retrieve the default values via corresponding getter messages:
<someBridgeClass> connectTimeout => that bridge type's connectTimeout <someBridgeClass> defineAckTimeoutMS => that bridge type's defineAckTimeout in milliseconds <someBridgeClass> executeAckTimeoutMS => that bridge type's executeAckTimeout in milliseconds
"connectTimeout" is relevant, when connecting to a remote machine;
"defineAckTimeoutMS" is the time expecco will wait for a confirmation after an action's code has been transferred to the bridge;
"executeAckTimeoutMS" is the time expecco waits for an acknowledge after sending an action-step's data (pin values). See note below.
To be effective, these default values must be set before a bridge connection is setup. You can either place these expressions into the "startup.rc" file (in the expecco installation folder), or into a test suite as an elementary (Smalltalk or JavaScript) action which is executed as post-load action or as a prepare action of a testplan.
The above control the default values for new bridge connections. You can also change an existing connection's parameter by sending the same messages to a concrete bridge.
Expecco 21.2:
- You may have to adjust the executeAckTimeout, if huge amounts of data are transfered.
- (we encountered this limit eg. when transferring a vector of 1 mio floats to the bridge)
Expecco 22.1:
- Expecco adjusts this automatically to match the amount of data.
I don't see the difference between DLL actions and bridged C-actions; when to use which?[Bearbeiten]
DLL action blocks - when executed - will ensure that the specified DLL (shared object) is loaded into the running expecco program and directly called from within expecco. In other words: it executes in the same address space as expecco.
On the other hand, C-Bridge actions are executed by a separate program (the "CBridge") and expecco communicates with that program via a socket connection.
Both mechanisms have advantages and disadvantages:
DLL actions:
(+) very fast call (no remote procedure call)
(+) very fast data transmission (especially, big vectors can be passed quickly)
(+) can be used if only the binary is present and no compiler toolchain is needed/wanted
(-) the called code may crash the expecco executable if it misbehaves or is provided with wrong arguments
(notice: although expecco tries to catch any illegal memory references, it may still happen, that a called DLL-function misbehaves and overwrites/manipulates memory which it is not supposed to. For example, if it overwrites any data of expecco, there may be a later crash in expecco.
(-) the called code may block expecco or slow down the user interface
(-) the called code may exit/terminate the current thread or the program and thus terminate expecco (i.e. make sure, it does not call "exit()
"). Although expecco sets the "atexit" handler, shared libraries may still find other means to terminate.
Bridged Calls:
(+) code runs completely isolated from expecco - the bridge may crash due to bad data or a bad library call, but expecco will remain alive. You'll get a fail-status of the corresponding action in expecco.
(+) additional data conversions or checks are easily added (in case the called function's interface requires complicated object structures, such as complex structures/unions, pointers to pointers, pointers to return values etc.)
(+) better support for debugging (writing log-entries, adding printfs or running the cBridge under a debugger)
(+) the expecco UI will not be blocked by a runaway bridged action
(+) the bridged action can be executed on the local or on a remote machine
(+) the bridged action can be executed on a different operating system or CPU architecture (eg. expecco runs on a Linux machine, the bridge on Windows or even in a RasperryPI)
(+) multiple bridged C actions can execute in parallel on multiple machines or in multiple processes on one machine
(-) the round trip times are quite long compared to direct DLL calls (in the order of milliseconds, in contrast to direct DLL calls, which run in the order of nanoseconds)
(-) multiple round trips if many fields of returned object references have to be accessed
(-) a C-compiler toolchain is needed (on the local or the target system, where the bridged C code is to be executed)
Despite the above mentioned performance differences, we highly recommend to use bridged C actions instead of direct DLL calls, iff the performance/round trip times are acceptable. The added safety due to the process boundaries makes it much easier to develop and maintain those.
How can I debug the code in the bridge[Bearbeiten]
Expecco does include some limited debugging support for bridged code, but the level of support is different between individual bridges:
- Python, Node, Smalltalk, Groovy
Support breakpoints and single stepping. Walkback and some access to remote objects. - C
Walkback and some access to remote objects.
To debug bridged C-code, we recommend executing the bridge inside a debugger; for example, under Unix, you would compile your framework with the "-g" (debug) option, then open a shell terminal window and start the bridge under a debugger (say "lldb" or "gdb") with:
lldb cBridge >> r --server --port 8899
then change the expecco settings for the CBridge to "Connect to an already running bridge" and also define the port there. Expecco will then connect to your debugged bridge instead of starting one itself. A similar setup is to be used when debugging with Eclipse or Visual Studio on Windows.
To debug Python code, you can check the "Disable Debugger" flag in the Python settings, and attach a Python debugger (eg. VSCode) to the running Python process.
Security Considerations[Bearbeiten]
Make sure that remote bridges are only reachable from inside your local network and only from trusted hosts. You should configure your firewall as appropriate, or run tests in an isolated network. You may also configure switches and routers to limit access, or use a secondary interface (alternative lan or wifi).
Another recommened option is to setup an ssh tunnel for the particular port(s) in question.
All bridges support command line arguments to only accept connections from a particular host; this option should be used if a remote bridge is started via a remote shell command.
[OS X Only] CrashReporter Dialog Boxes[Bearbeiten]
You may get annoying dialog boxes telling about "Unexpected Termination" whenever a bridge is hard terminated via the "Terminate" button in the debugger.
This is a "feature" of the OS X system, not controlled by expecco.
You can disable this by executing the following command in a console terminal:
defaults write com.apple.CrashReporter DialogType none
Back to FAQ