Distributed Isolator
Introduction
The purpose of Isolator, my framework for running isolated (sandboxed) code is to be able to, well, run code (known as plugins) on distributed, isolated instances (hosts).
I initially created two isolation hosts:
- Process (ProcessIsolationHost): a new process is created to host and run the plugin
- Assembly Load Context (AssemblyLoadContextIsolationHost): a new assembly and assembly load context are created to host and run the plugin
Now I'm introducing a new one, that allows to execute code on another machine: ClientIsolationHost. I'll show you how to use it now.
Client Host Isolation
The idea is to be able to run code on a different machine, while keeping the same capabilities:
- We pass an existing plugin instance to the host which is then handled transparently
- Modified context properties are returned from the host to the caller
- Standard output and error are also returned
- The result value from the plugin's execution is returned too
We want to be able to specify a port, on the server end, and a host and port, on the client end. For that, we introduced a few new classes, besides ClientIsolationHost:
- IsolationHostClient: client part, no need to explicitly use it, it is used behind the scenes by ClientIsolationHost
- IsolationHostServer: server part, we need to use it
- IReceiver/TcpReceiver: take care of the low-level reception (must match the transmitter), no need to worry about it
- ITransmitter/TcpTransmitter: the low-level transmission (must match the receiver), no need to worry about it
Example usage follows.
On the server end:
using var server = new IsolationHostServer(); using var cts = new CancellationTokenSource(); await server.ReceiveAsync(port: 5000, cts.Token); System.Console.ReadLine();
And on the client:
using var host = new ClientIsolationHost(host: "<someserver>", port: 5000); var plugin = new HelloWorldPlugin(); var context = new IsolationContext(); var result = await host.ExecutePluginAsync(plugin, context);
Please note that on the server process, all you need to do is execute IsolationHostServer.ReceiveAsync. When you are done with it, just call Dispose or signal the CancellationToken parameter (more on cancellation tokens here).
As of now, it uses pure TCP/IP for communication, which means messages are turned into byte arrays and sent over the wire, but the mechanism is pluggable and more methods can be easily implemented.
The same ClientIsolationHost instance can be used to send multiple execution requests, of course, all to the same host and port. When you dispose of it, you will no longer be able to send requests. This class is used inside ClientIsolationHost so we generally don't need to worry about it.
Other Improvements
I made the serialisation process configurable, by means of a new abstraction, ISerializer, that only has a single implementation, IsolationJsonSerializer, that uses System.Text.Json. You don't need to do anything to use it, it is the default. The new TcpReceiver and TcpTransmiter use it.
Comparing With Other Hosts
Because this host is transmitting code to a (possibly) different machine, there is some latency involved. All should work the same, regardless of that. This one, of course, will provide a much higher degree of isolation.
Conclusion
All in all, I think this is a good addition to my framework. It is beginning to grow and add useful functionality. Code is already available in GitHub and in NuGet too.
It's missing some more error checks, ability to return exceptions, and maybe some polishing, possibly I'll make some changes during the next weeks.
As always, looking forward to hearing your thoughts!
Comments
Post a Comment