Process Management

Finsemble gives you the ability to balance performance and resource usage through configuration. This is done by using process affinities to group several windows into a single renderer process. This approach involves specifying in advance which components should be grouped together.

By default, Finsemble will reduce memory footprint by grouping all windows into the same affinity that created them (i.e., the Window Service). We strongly recommend that you override this default by setting an affinity for each of your components.

This tutorial discusses Finsemble's process models and gives you the tools to manage them.

Single process per window

To understand Finsemble's process model, it's helpful to understand how Chromium—the engine of our container—handles its processes.

Process in Chrome illustration

The diagram above shows Chrome's default process model. The window is the browser process: in addition 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 usually has a separate renderer process. A renderer 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 renderer processes for several reasons, but one of the biggest is that it isolates tabs from each other. In other words, they don't share fate—tab A can't crash tab B. Generally speaking, the number of tabs maps to the number of renderer processes (though there are exceptions). To understand why this model is desirable, recall the last infinite loop you wrote—your tab crashed but Chrome continued running.

Unfortunately, this process model introduces a larger memory footprint.

A single process per window:

  • Isolates windows from each other and prevents failures in one instance from affecting others;
  • Is resource intensive.

Sharing renderer process

An alternative is to force windows to share a single renderer process by grouping them into an "application." There is no such construct in Electron, but we have created it in Finsemble. In this scenario, Finsemble can open N applications and an application can have N windows. There is only one renderer process per application. By allowing multiple windows to share a single renderer process, the memory usage is dramatically reduced compared to the default process model of Chrome or Electron.

Process in a Finsemble application

In this scenario, all windows are child windows of a single "application." As a result, they all share a renderer process. As mentioned earlier, each renderer 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 start-up 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 renderer process.

Sharing renderer processes:

  • Has less memory overhead;
  • Results in large renderer processes, which impact responsiveness.

Affinity: Manage processes intelligently

You can control which processes are isolated and which are grouped through affinity. In this way, you can strike a balance between performance and memory footprint.

Setting affinity with the Launcher Client

If the component is doing a lot of computation or rendering that may slow other components, we recommend isolating its renderer process by randomly generating an affinity for your component in the call to FSBL.Clients.LauncherClient.spawn (see below).

Example of dynamic affinity via spawn.

	FSBL.Clients.LauncherClient.spawn('Welcome Component', { affinity: 'test' })

Setting affinity with configuration

Affinity can be controlled by configuration. Add an affinity configuration to each component's config entry. The affinity config is a string. Group components together thoughtfully by setting them to the same affinity value.

The example below shows how we isolate our system components from the rest of Finsemble:

"App Launcher": {
	"window": {
		...
		"affinity": "systemComponents",
		...
	},
"Toolbar": {
	"window": {
		...
		"affinity": "systemComponents",
		...
	},
}

Affinity best practices

When allocating renderer processes through affinity, ask yourself the following 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 smart desktop to function (e.g., toolbars, menus)?

If the answer to any of these questions is yes, the component is a good candidate for its own renderer process.

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


check   Affinity involves specifying in advance which components should be grouped together. This approach reduces the memory footprint and increases the stability of your application. We recommend giving each component a specified affinity.
 

Further reading

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

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