Process Splintering

Splintering is the approach that Finsemble uses to intelligently manage and allocate your application's processes. It's extremely flexible and entirely config-driven. This tutorial is all about window creation: how we do it, why you should think about it, and why we think that this approach is the best.

How splintering works

The diagram above shows a high-level overview of the creation of new windows in Finsemble. Finsemble has a pool of SplinterAgents sitting in the background waiting for spawn requests. Each SplinterAgent is capable of spawning specific components or services (but not both). When FSBL.Clients.LauncherClient.spawn is called, there is a check to see if aSplinterAgent is available that can spawn the component. If so, request is passed to the agent. If not, a new SplinterAgent is created, which then processes the request. At that point, your window is created. This entire process takes a dozen milliseconds or so.

To understand why we've gone down this route, it's helpful to understand a little more about Chromium and OpenFin manage processes.


Browser vs. render process in Chromium

The diagram above shows Chrome. The window is the browser process: in additional to many other responsibilities, the browser process manages communication with all of the hidden and visible tabs. You can think of it as the brains of Chrome. Each tab has a separate render process. A render process has a single JavaScript event loop; it also handles the actual painting of all of the pixels on the page. Chrome separates each tab into separate render processes for several reasons, but one of the biggest is that it isolates tabs from each other. In other words, tab B cannot crash tab A. Generally speaking, the number of tabs maps to the number of render processes (though there are exceptions—check the recommended reading section for more). To understand why this model is desirable, recall the last infinite loop you wrote—your tab crashed but Chrome continued running.

Browser vs. render process in Openfin

In OpenFin, there is one browser process per run-time instance. The number of render processes depends on how many applications are open in the runtime. The runtime can open N applications. However, there is only one render process per application. An application can have N windows. By allowing multiple windows to share a single render process, OpenFin dramatically reduces memory usage compared to Electron. The reduction in memory footprint is tremendous.

This approach is good for smaller applications. As applications grow in size, however, there are additional considerations.

Process management strategies

This graphic illustrates a "one to many" process. In this scenario, all windows are child windows of a single application. As a result, they all share a render process. When the Finsemble development team first started, this was the strategy that we used. However, if your application grows beyond ten windows or so, this approach introduces performance issues. As mentioned earlier, each render process has a single event loop, single renderer, etc. This means that window A can impact the timing of code execution and rendering in window B. In practice, this makes application startup look like you're knocking a row of dominoes over. The first one has to fall (load and paint) before the second one can.

In addition to generalized sluggishness, this strategy will eventually bump up against hardcoded memory caps imposed by the render process (~1GB).

This graphic illustrates a "one to one" mapping. In this model, each window is spawned as its own application. Each window has its own render process. This is how Chrome (and Electron) do things. The biggest downside to this model is that your application as a whole will take up significantly more memory. This is why OpenFin opted to allow for windows to share a render process.

The model that we employ in Finsemble is called "splintering." This approach involves specifying in advance which components should be grouped together. If desired, the developer can also cap the render processes to a specific number of components (e.g., only three advanced charts per process). This approach is very flexible, and provides the developer with the advantages of both of the above approaches.

Best practices

Our strategy for splitting out components is to ask three questions:

  1. Is the component heavy? That is, does it use an inordinate amount of memory or CPU?
  2. Does the work that the component is doing interfere with the performance of other components?
  3. Is the component necessary for the application to function (e.g., toolbars, menus)?

If the answer to any of these questions is yes, the component is a good candidate for splintering. If it's a heavy component, it's probably not _so_ heavy that it needs to be isolated. If the only problem is that it uses a lot of memory, simply cap the SplinterAgent at something reasonable. That said, "reasonable" is subjective; it might require trial and error to refine.

If the component is doing a lot of computation or rendering that may slow other components, we recommend isolating its render process. This can be done by adding setting maxWindowsPerAgent to 1. We do this for our Central Console.

If your component is necessary for the application to function (e.g., toolbars, menus, etc.), we recommend isolating them from other components. This way, if a new component fires off an infinite loop, you can still quit the application.

Splintering isn't a panacea, but we believe it represents the best approach for managing the creation of new windows. Creating a new agent has overhead, particularly on slower machines. Iterate to find the right configuration for the users you're supporting.

What follows is a deeper discussion into the hows and whys of process splintering.


Config

The config is the way that you will interface with splintering. On start-up, the LauncherService will read this config, and all calls to FSBL.Clients.LauncherClient.spawn will obey your config.

The example code below shows a hypothetical example. Your seed project's splintering config can be found inside of configs/core/config.json.

    "splinteringConfig": {
        "comment": "A SplinterAgent is just an OpenFin application that is capable of spawning specific components or services. If you try to spawn a component/service that one of these agents does not cover, it will be spawned by the defaultAgent. You can also specify maxWindowsPerAgent if you would like to limit your agents to some ceiling. This is useful when you have a particularly heavy component.",
        "enabled": true,
        "splinterAgents": [
            {
                "agentLabel": "systemComponents",
                "components": [
                    "Toolbar",
                    "App Launcher",
                    "Workspace Overflow Menu",
                    "Workspace Management Menu",
                    "Finsemble Linker Window",
                    "defaultAuthentication"
                ]
            }
        ]

SplinterAgent Config

Property Type Default Description
agentLabel String null The name that your agent should have when it's created. A unique ID is appended to this name. This exists so that you can easily figure out which agent is responsible for which components.
components Array [] An array of component names. The name is the primary key inside of your component config.
services Array [] An array of services names. Agents that house services cannot also hold components.
maxWindowsPerAgent Number Infinity If supplied, this will cap each agent to this number. When that number is reached, it will create an additional agent to handle future calls to spawn.

Adding a new agent

To create a new SplinterAgent, simply add an additional agent to the array in your config.

Here's an example of a new agent that can only open two kinds of components ("Heavy 1" and "Heavy 2"). When the agent has four child windows, a second agent will come online to handle future requests.

{
    "agentLabel": "heavyComponent",
    "components": [
        "Heavy 1",
        "Heavy 2"
    ],
    "maxWindowsPerAgent": 4
}

Further reading

More information about Finsemble's build process can be found here.

More information about how Finsemble is configured can be found here.

For more information about Chromium, their documentation provides additional context.