Run user code in the server

Hi!

I have a project in which a server (written with Ktor) is a hub for multiple devices which expose either outputs or inputs. The user, through a web frontend, should be able to connect outputs to inputs of the different devices.
A naïve approach is to just allow linear connection (outputs and inputs have predetermined range). But I would like to allow the users to write code snippets that define how to map outputs to inputs, e.g.

linearScale(0, 100, -100, 100)(if (source > 5) { source * 3 } else { source + 5 })

or something like that. The inputs and outputs are flows.

I can’t wrap my head around how to get user written and evaluate it in a specific context.
I’ve tried duplicating the kotlin-scripting-examples in my project, but I keep on getting NoClassDefFoundError when I try to run them. I’ve tried to duplicate the JSR 223 and the scripting host example, both without success. I’m using Kotlin 1.3.72 and I can’t upgrade to 1.4.0 because it breaks other libraries I use.

Things that might help:

  1. Example that does something similar
  2. Other ideas for creating user mapping between values
  3. Any pointer on why I can’t run the scripting examples

Thanks!

  1. I don’t know any, but I don’t know much about this topic. Maybe someone else can help you here.

  2. You could create your own system with a number of configurable presets, eg. linear scale, logarithmic, exponential, etc

Can you be a bit more specific? NoClassDefFoundError should normally tell you which class is missing. That normally indicates that something in your dependencies is wrong. One guess I have is that you are possibly missing a dependency on the kotlin std-lib somewhere. The examples don’t include it in the build files, but this is only possible since kotlin 1.4. If you are using 1.3.72 you still need to add that dependency manually.
I just downloaded the script-examples and they run fine on my machine (all tests run ok, except the JSR tests, see issue here) so the examples are mostly working fine.

1 Like

It looks similar to the project we are doing right know (a collaboration including several research organizations and our JetBrains Research lab at MIPT). The idea is to use two uni-directional reactive streams (kotlin Flow for the kotlin part) both for device communication and integration of other tools. The device servers prototype is done here. It already features device API, ktor-based server, and so-called magix-gate to communicate with the common event loop. You are welcome to join the collaboration (we can discuss it in slack in #science channel).

Now, for scripting - It is generally bad idea, because

  1. it is generally a bad idea to allow to run any code on the server-side. It could be malicious, or just faulty. Kotlin scripting allows some degree of environment isolation, but it still is tricky.
  2. The kotlin compiler is huge, it will create a tremendous overhead on any operations.

I would say, it is much easier to create filters and transformations on the event stream on the client-side or inject transformers into the event loop.

2 Likes

Hi!

Thank for your input, it does sound indeed like the projects are similar, and I’m wondering what kind of devices do you connect to and who are the intended users.

I realise that allowing scripting has performance issues, but my assumption (which is unbased, just a guess) is that the users will write code very rarely, and the code will not be compiled on each execution. Regarding security, the system is intended to be run on a dedicated and isolated network, so it’s not the highest concern, but thank you for bringing this up.

I would like to hear more about how you represent transformations, connect them etc. while still keeping the system versatile.

I will recreate the failing code. One class that was missing was the “Jsr223ScriptingHost” something.
Do you mean that these are not supported in 1.4.0, or that they merely have different dependencies than in 1.3.72? If the latter, do you know how I can find out what are the dependencies?

We are aiming for experimental physics and industry. But since the architecture is so simple and scaleable, I think it is quite possible to create a universal interface that would suit small experiments, industry scale and even game devices. The device servers share very similar IO requirements and operation workflow everywhere. It would be an interesting experience to have all the devices from different areas to be plugable to the same system. Even now we seldom use some smart home sensors and even toys in science since they are much cheaper and better developed than special scientific devices.

All we need to make it work is to agree on the protocol.

As I said, I don’t really use those libs myself so I’m not 100% sure. I know that the kotlin compiler will add a dependency on the stdlib automatically since 1.4.0. JSR223ScriptingHost isn’t part of the stdlib, so this wasn’t the issue.

Looking at the changes made to the example project you can see that the org.jetbrains.kotlin:kotlin-scripting-jvm-host:1.4.0 dependency used to be org.jetbrains.kotlin:kotlin-scripting-jvm-host-embeddable:1.3.70 for the runtime. My guess is that kotlin-scriptin-jvm-host just provides the public API (so it should be used for libraries and such) and kotlin-scripting-jvm-host-embeddabl provides the actual implementations (for applications and tests). The changes to dependency names are documented here: https://kotlinlang.org/docs/reference/whatsnew14.html#artifacts-renaming

1 Like

You can see my examples of kotlin-scripting here and here. The last one already is migrated to 1.4. Both do not use jsr223, but you should not use it anyway unless you want to make something that specifically adheres to jsr223 specificaition.

Still, in its current form, it should not be used for remote execution for security reasons.

1 Like

Thank you! This is very helpful and I will take a look at the change history to figure this out.

I’ve asked to join the slack workspace.

My research group is working with IoT devices, mDNS is used for service discovery and OSC is used for communication. I can try and write a device firmware that will communicate with your platform, it would be really cool if we could use it.

Looking forward to it. Right now we are devising specifications and default protocols for the system, so any input would be welcome.

I don’t like asking “users” to write programs, or giving them the ability to write undetectable infinite loops or consume unbounded resources, both of which are inescapable if you give them a Turing complete language in which to write things that run on your server.

But that doesn’t mean you have to be inflexible. I suggest you think of this “connection mapping” more like a spreadsheet and less like a program

That will certainly require providing an expression language, but not a full programming language in which you could write arbitrary loops.

JEXL is a good place to start, although it’s also not so hard to write one from scratch. One of my pet peeves is that programmers these days are too reluctant to do so :slight_smile:

If we’ve started to talk about expression interpretation, we’ve recently added a powerful feature called MST (mathematical syntax tree) in kmath. @postovalovya wrote an article about that. The idea is to represent limited mathematic operations in a dynamic AST. It could be used to store boolean logic as well. The feature is in the prototype stage, but it could be used to implement custom conditions and transformations remotely. I still think that in the case of devices, transformations should not be run in-place, but instead, events should be transmitted as-is.

I’ve worked on or had been adjacent to several projects in the past that chose to define their own programming language for business logic instead of creating an EDSL in a pre-existing language. While I agree with your analysis of the risks, the current alternative is that my users write their own complete programs in Processing or C or something as low level as that, so the risk of users writing insecure or buggy code is already there. A good DSL, embedded in a nice language like Kotlin should reduce that risk.

What do you mean by “transformations should be run in place”? as in on the device?
I think that the browser audio API would actually be very useful for me, but I don’t want run the transformations at the browser, it’ll add two more “leaps” for event and we already have some reservations regarding the latency of the whole system (well, an older more ad-hoc implementation of it anyway).

The discussion is moved to slack. But to close this thread here, I want to write some kind of conclusion. We are currently discussing event streams and how to implement them without affecting latency. I think that the latency of arbitrary script execution will be much larger than the one for event routing.

1 Like