Chapter 2: Hello World configuration refined#

Our HelloWorld example from chapter 1 had a very “sloppy” configuration approach. In this chapter we will refine the configuration and show, what possibilities score::mw::com provides to load configurations.

Files/artifacts used#

The bazel project for this chapter is located in score/mw/com/doc/tutorial/chapter_2. It contains the following files:


File Name

Description

BUILD

This file contains bazel targets for this example.

consumer/consumer.cpp

Implementation of the service consumer app. The main() for the consumer

consumer/consumer.h

Header (empty - we just always want to have cpp/h pairs) of the service consumer.

consumer/consumer_config.json

This file contains the configuration for score::mw::com for the consumer app.

consumer/logging.json

This file contains the configuration for the logging system used by score::mw::com

provider/provider.cpp

Implementation of the service provider.

provider/provider.h

Header (empty) of the service provider.

provider/logging.json

This file contains the configuration for the logging system used by score::mw::com

provider/mw_com_config.json

This file contains the configuration for score::mw::com for the provider app.

hello_world_service.cpp

This file is empty as the service interface is completely defined in the header.

hello_world_service.h

This file contains the definition of the service interface.


How to run the example#

The steps are almost the same as in chapter 1 (minus the chapter adaptions in paths)! We will explicitly mark the relevant differences here. To run the example (on host/Linux), you can use the following bazel commands:

# Build the provider and consumer targets
bazel build //score/mw/com/doc/tutorial/chapter_2:provider-tar
bazel build //score/mw/com/doc/tutorial/chapter_2:consumer-tar

The bazel build above creates tar-archives for the provider application and the consumer application. They should then be located under bazel-bin/score/mw/com/doc/tutorial/chapter_2/provider-tar.tar resp. bazel-bin/score/mw/com/doc/tutorial/chapter_2/consumer-tar.tar.

To run provider/consumer apps, extract both archives (e.g. in a tmp-directory):

mkdir -p /tmp/tutorial/chapter_2
tar -xf <project dir>/bazel-bin/score/mw/com/doc/tutorial/chapter_2/provider-tar.tar` -C /tmp/tutorial/chapter_2/
tar -xf <project dir>/bazel-bin/score/mw/com/doc/tutorial/chapter_2/consumer-tar.tar` -C /tmp/tutorial/chapter_2/

… then start the service-provider application in the 1st terminal:

cd /tmp/tutorial/chapter_2/opt/HelloWorldServer
bin/provider_app

and the service-consumer in the 2nd terminal (this is notably different to chapter 1, because we now provide a specific configuration for the consumer app, which is loaded via the –service_instance_manifest argument):

cd /tmp/tutorial/chapter_2/opt/HelloWorldClient
bin/consumer_app --service_instance_manifest ./etc/consumer_config.json

Provider application#

The code for the provider application is unchanged compared to chapter 1. The only difference is the configuration, which is now split into a provider specific configuration and a consumer specific configuration. However, the provider specific configuration is still the same as in chapter 1.

Consumer application#

The consumer application is also almost identical to the consumer application of chapter 1. The only difference is, that we now load a specific configuration! So, if you look at line 37 of consumer.cpp file, you will see that we added a specific call:

Listing 15 consumer/consumer.cpp#
    score::mw::com::runtime::InitializeRuntime(argc, argv);

So, there is a specific call to initialize the score::mw::com runtime, which takes the command line arguments of the consumer app as input. And when you look, how we start the consumer app in this chapter, you will see that we provide a specific argument –service_instance_manifest ./etc/consumer_config.json to the consumer app. I.e. the –service_instance_manifest argument provides a mechanism to give the path to a specific configuration file.

Configuration#

Loading of configurations#

Here are the details regarding loading of configurations for score::mw::com applications:

Typically – as you have seen in our HelloWorld example in chapter 1 – there is no need for an explicit call to score::mw::com::runtime::InitializeRuntime(). The configuration gets implicitly loaded by score::mw::com, when it is needed. This happens within the 1st call to a score::mw::com API, which needs the configuration.

This implicit/automatic loading expects a configuration file named mw_com_config.json to be located in the path ./etc relative to the current working directory of the application. In the HelloWorld example of chapter 1, we did exactly that. We provided a single configuration file named mw_com_config.json in the ./etc path relative to the current working directory of the provider and consumer app. Same we did in chapter 2 for the provider app. But for the consumer app, we now provide a specific configuration file named consumer_config.json in the ./etc path relative to the current working directory of the consumer app. This is why we need to explicitly call score::mw::com::runtime::InitializeRuntime(argc, argv) in the consumer app, to load this specific configuration file.

If you commented out the score::mw::com::runtime::InitializeRuntime(argc, argv) call in the consumer app, you would run into an error like this, when starting the consumer app:

q240165@cmucw1079481:/tmp/tutorial/chapter_2/opt/HelloWorldClient$ bin/consumer_app --service_instance_manifest ./etc/consumer_config.json
2026/07/02 10:43:53.1833727 39169650 000 ECU1 EXP1 lola log fatal verbose 7 Parsing config file ./etc/mw_com_config.json failed with error: An error occurred during parsing. :  Failed to open file  . Terminating.
Aborted (core dumped)

So here are some best practices regarding configuration files for score::mw::com applications:

  • typically go for the default/implicit approach: Put the configuration file mw_com_config.json in the ./etc path

relative to the current working directory of the application. - if you have a strong reason to have a different configuration file name or path, then go for the explicit approach:

  • either call score::mw::com::runtime::InitializeRuntime(argc, argv) in your application and provide the path to the configuration file via the –service_instance_manifest command line argument.

  • or - for the real “power-user” - call score::mw::com::runtime::InitializeRuntime(const RuntimeConfiguration& runtime_configuration), where you can provide a RuntimeConfiguration object, which allows you to set the path to the configuration file programmatically.

  • when you decide to go for the explicit approach, you should do the loading of the configuration as early as possible in your application, avoid the implicit loading of the configuration by score::mw::com to kick in! Because, once a configuration is loaded, it can’t be changed anymore. Any subsequent call to score::mw::com::runtime::InitializeRuntime() will return an error, because the configuration is already loaded and can’t be changed anymore.

Separate configuration files for provider and consumer#

The even bigger change between our HelloWorld example of chapter 1 and the HelloWorld example of chapter 2 is, that we now have separate configuration files for the provider and the consumer! This is the expected approach for real-world applications. Sharing a single configuration file between provider and consumer is error-prone and was only a shortcut for our HelloWorld example!

If you looked closer at the log output of both the provider and the consumer app in our HelloWorld example from chapter 1, you would see something like this:

2026/07/02 15:21:24.8484053 205672912 000 ECU1 EXP1 lola log info verbose 2 No explicit applicationID configured. Falling back to using process UID. Ensure unique UIDs for applications using mw::com.

The root cause for this log message is, that both the provider and the consumer app used the same configuration file, which did not contain a global section with an applicationID property! The global`section is a top-level section in the configuration file, which holds application-wide configuration properties. This section is optional, since all its properties do have more or less sensible defaults. One of these properties is the `applicationID, which is a unique identification for a score::mw::com application. If this property is not set in the configuration, score::mw::com will fall back to using the process UID as application ID. But this then implicitly requires, that if you start the same executable (using the same configuration without an explicitly set applicationID) multiple times, you need to ensure that each process has a unique UID!

This is not the case for our HelloWorld example of chapter 1, because we started the provider and the consumer app under the same user. Thus, this already opens the door for potential problems, which are extremely hard to debug!

Note

In the specific case of our HelloWorld example in chapter 1, it is luckily not a problem, because we have a single provider and a single consumer falsely sharing the same applicationID. But if we would have multiple consumers, which all share the same applicationID and consume the same service instance, we would run into problems! Hint: Each of the consuming applications stores some information within the shared-memory control-section of the provided service instance, needed for error-handling and restart control. The key for this storage is the applicationID of the consuming application. If multiple consumers share the same applicationID, they will overwrite each other’s information in the shared-memory control-section of the service instance!

That means: The global section, which is optional in theory but in practice should always be present in the configuration file, needs to be application specific. So this basically rules out sharing a configuration file between score::mw::com applications.

Another benefit of having separate configuration files for provider and consumer is, that you can be more concise in the configuration file!

Let’s now just compare the configuration files of the provider and the consumer app in our HelloWorld example of chapter 2 - here especially the service-instance deployment part!

This is, how the provider app configuration looks like in mw_com_config.json with regard to the service instance deployment:

Listing 16 provider/mw_com_config.json#
              "numberOfSampleSlots": 10,
              "maxSubscribers": 3

Because the provider app is responsible for the resource allocations, it needs to know, how many sample slots it needs to reserve for the “message” event and how many maximum subscribers it needs to support for the “message” event!

If you now look at the consumer app configuration in consumer_config.json with regard to the service instance deployment, you will see, that it is much smaller and more concise:

Listing 17 consumer/consumer_config.json#
          "binding": "SHM"

As you see, the consumer app configuration does not need to know about the number of sample slots and the maximum number of subscribers for the “message” event. This is because the consumer app does not need to allocate any resources for the service instance. So it does not need to define these properties in the configuration! In essence this has to do with the fact, that you always look at the configuration either in the role of a provider or in the role of a consumer. Depending on your role, different properties are relevant and need to be defined in the configuration. This is explained in detail in the configuration documentation here.

Summary#

A short summary of what we have learned in this chapter:

  • Don’t share configuration files between score::mw::com applications. Each application should have its own configuration file with explicitly set applicationID in the global section.

  • When possible: Use the default/implicit configuration loading approach, which expects a configuration file named mw_com_config.json in the ./etc path relative to the current working directory of the application.

  • If you need to use a different configuration file name or path, do the explicit loading/initialization right at the beginning of your application (e.g. via score::mw::com::runtime::InitializeRuntime(argc, argv)) before any other score::mw::com API is called.