Using linux standard library Thread

Hello Community,

I have a customer that wants to implement a linux thread deployed from exOS AS project.
I don’t have any experience with threads and not that much in exOS, that why i’m asking here.
For instance, I don’t have a lot of information, but it seems the exOS package stop running
with that kind of message, as soon as the code try to create a thread:

[ExDeployment]: Runtime service gXXX_0.0.runtime failed during operation! Component gXXX_0 will be aborted du to error handling settings! - checking gXXX_0.0.runtime.log…

He does not find something usefull in this .runtime.log file.

  1. Is it possible to use thread from standard linux library with exOS?
  2. Is there something special (in the context of exOS) to do for using it ?
  3. What could be the diagnostics files / information needed to help him ?

Best regards

Rémi

Generally speaking, when using exOS, and you have a problem with the data communication, check the ExData logger, the ExDeploy messages you refer to comes from the deployment mechanism that just tells you that “something went wrong”, but not how. The ExData will have more information on the process itself. Especially when you have problems getting everything to work, and before you start exchanging cyclic data, you can adjust your logger level to DEBUG, VERBOSE, and such to get more information. Please see exOS FAQ & Getting Started - Share Info & Ideas - B&R Community (br-automation.com) for more info on troubleshooting.

To your question: You can run exOS with pthreads on the Linux environment, only take care to handle the same datamodel instance in the same thread. Theres no implicit thread safety in creating datasets in one thread and handling them in another for example. It is generally possible to access the same data from Automation runtime from different threads, I have no ready-made package compiled for that, but here’s a sample that should give you some ideas on how it works:

#include <unistd.h>
#include <pthread.h>
#include <string.h>
#include <stdlib.h>

#include "termination.h"

#define EXOS_ASSERT_LOG logger
#include "exos_log.h"
#include "exos_threads.h"

#define APP_ASSERT(_expected_, _expression_)                                                 \
    if (_expected_ != _expression_)                                                          \
    {                                                                                        \
        exos_log_error(logger, "%s Error in file %s:%d", #_expression_, __FILE__, __LINE__); \
    }

#define SUCCESS(_format_, ...) exos_log_success(logger, EXOS_LOG_TYPE_USER, _format_, ##__VA_ARGS__);
#define INFO(_format_, ...) exos_log_info(logger, EXOS_LOG_TYPE_USER, _format_, ##__VA_ARGS__);
#define VERBOSE(_format_, ...) exos_log_debug(logger, EXOS_LOG_TYPE_USER + EXOS_LOG_TYPE_VERBOSE, _format_, ##__VA_ARGS__);
#define ERROR(_format_, ...) exos_log_error(logger, _format_, ##__VA_ARGS__);

typedef struct
{
    char *thread_name;
    char *request_name;
    char *response_name;
    uint8_t request_data;
    uint8_t response_data;

    exos_log_handle_t logger;
    exos_artefact_handle_t artefact;
    exos_value_handle_t value;
    exos_value_handle_t response;

    int sleep_time;

} thread_data_t;

static void valueChanged(exos_value_handle_t *value)
{
    thread_data_t *data = (thread_data_t *)value->user_context;
    exos_log_handle_t *logger = &data->logger; //needed for the LOG macros

    VERBOSE("value %s changed!", value->name);

    //the only thing we subscribe to is the request_data in each thread
    //connection between the exos_value_handle_t *value and the actual response_data / request_data is done in the exos_value_init for each thread
    data->response_data = data->request_data;

    //we wait a configured amount of time
    usleep(data->sleep_time);

    //and the only thing we publish is the response
    exos_value_publish(&data->response);
}

static void valuePublished(exos_value_handle_t *value, uint32_t queue_items)
{
    thread_data_t *data = (thread_data_t *)value->user_context;
    exos_log_handle_t *logger = &data->logger; //needed for the LOG macros

    VERBOSE("value %s published! queue size:%i", value->name, queue_items);
}

static void valueConnectionChanged(exos_value_handle_t *value)
{
    thread_data_t *data = (thread_data_t *)value->user_context;
    exos_log_handle_t *logger = &data->logger; //needed for the LOG macros

    INFO("value %s changed state to %s", value->name, exos_state_string(value->connection_state));

    switch (value->connection_state)
    {
    case EXOS_STATE_DISCONNECTED:
        break;
    case EXOS_STATE_CONNECTED:
        break;
    case EXOS_STATE_OPERATIONAL:
        break;
    case EXOS_STATE_ABORTED:
        ERROR("value %s error %d (%s) occured", value->name, value->error, exos_error_string(value->error));
        break;
    }
}

static void connectionChanged(exos_artefact_handle_t *artefact)
{
    thread_data_t *data = (thread_data_t *)artefact->user_context;
    exos_log_handle_t *logger = &data->logger; //needed for the LOG macros

    INFO("application changed state to %s", exos_state_string(artefact->connection_state));

    switch (artefact->connection_state)
    {
    case EXOS_STATE_DISCONNECTED:
        break;
    case EXOS_STATE_CONNECTED:
        break;
    case EXOS_STATE_OPERATIONAL:
        SUCCESS("Threads operational!");
        break;
    case EXOS_STATE_ABORTED:
        ERROR("application error %d (%s) occured", artefact->error, exos_error_string(artefact->error));
        break;
    }
}

void *thread_function(void *args)
{
    thread_data_t *data = (thread_data_t *)args;
    exos_log_handle_t *logger = &data->logger; //needed for the LOG macros

    exos_log_init(logger, data->thread_name);

    SUCCESS("Starting thread %s ..", data->thread_name);

    EXOS_ASSERT_OK(exos_artefact_init(&data->artefact, "Threads"));
    EXOS_ASSERT_OK(exos_value_init(&data->value, &data->artefact, data->request_name, &data->request_data, sizeof(data->request_data)));
    EXOS_ASSERT_OK(exos_value_init(&data->response, &data->artefact, data->response_name, &data->response_data, sizeof(data->response_data)));

    data->artefact.user_context = args;
    data->value.user_context = args;
    data->response.user_context = args;

    EXOS_ASSERT_OK(exos_artefact_register_threads(&data->artefact, connectionChanged));
    EXOS_ASSERT_OK(exos_value_register_subscription(&data->value, valueConnectionChanged, valueChanged));
    EXOS_ASSERT_OK(exos_value_register_publisher(&data->response, valueConnectionChanged, valuePublished));

    while (true)
    {
        exos_log_cyclic(logger);
        EXOS_ASSERT_OK(exos_artefact_cyclic(&data->artefact));

        if (is_terminated())
        {
            SUCCESS("Threads %s terminated, closing..", data->value.name);
            break;
        }
    }

    EXOS_ASSERT_OK(exos_value_unregister_publisher(&data->response));
    EXOS_ASSERT_OK(exos_value_unregister_subscription(&data->value));
    EXOS_ASSERT_OK(exos_artefact_unregister(&data->artefact));

    //first delete the values, then the artefact
    EXOS_ASSERT_OK(exos_value_delete(&data->value));
    EXOS_ASSERT_OK(exos_value_delete(&data->response));

    EXOS_ASSERT_OK(exos_artefact_delete(&data->artefact));

    //finish with deleting the log

    exos_log_delete(logger);

    if (data->request_name)
        free(data->request_name);
    if (data->response_name)
        free(data->response_name);
    if (data->thread_name)
        free(data->thread_name);

    return NULL;
}

void set_thread_data(thread_data_t *data, int sleep_time, const char *thread_name, const char *request_name, const char *response_name)
{
    data->sleep_time = sleep_time;
    data->thread_name = strdup(thread_name);
    data->request_name = strdup(request_name);
    data->response_name = strdup(response_name);
}

int main()
{
    pthread_t thread_t1;
    pthread_t thread_t2;
    pthread_t thread_t3;

    thread_data_t t1_data = {};
    thread_data_t t2_data = {};
    thread_data_t t3_data = {};

    exos_log_handle_t main_logger;
    exos_log_handle_t *logger = &main_logger; //needed for the LOG macros

    exos_log_init(logger, "Threads_Main");

    SUCCESS("starting Threads application..");

    catch_termination();

    set_thread_data(&t1_data, 0, "Threads_T1", "request.t1", "response.t1");
    APP_ASSERT(0, pthread_create(&thread_t1, NULL, thread_function, &t1_data));

    set_thread_data(&t2_data, 200000, "Threads_T2", "request.t2", "response.t2");
    APP_ASSERT(0, pthread_create(&thread_t2, NULL, thread_function, &t2_data));

    set_thread_data(&t3_data, 500000, "Threads_T3", "request.t3", "response.t3");
    APP_ASSERT(0, pthread_create(&thread_t3, NULL, thread_function, &t3_data));

    while (true)
    {
        exos_log_cyclic(logger);

        if (is_terminated())
        {
            SUCCESS("Threads application terminated, closing..");
            break;
        }
        usleep(10000);
    }

    //wait for the threads to finish
    APP_ASSERT(0, pthread_join(thread_t1, NULL));
    APP_ASSERT(0, pthread_join(thread_t2, NULL));
    APP_ASSERT(0, pthread_join(thread_t3, NULL));

    //finish with deleting the log
    exos_log_delete(logger);
    return 0;
}

Happy coding :slight_smile:

Hello,

Customer’s feedback said that the issue was solved by changing library c++ std::thread by pthread. So thank you @patric.thysell

Best regards

Rémi