mirror of
https://github.com/open62541/open62541.git
synced 2026-10-17 04:42:09 +08:00
fix(pubsub): clean up failed writer creation and deferred deletion
Roll back writers whose information model representation fails. A deferred connection or WriterGroup delete asks the lifecycle callback only once, retries vetoed writers, rejects new ones and completes a manager stop.
This commit is contained in:
committed by
Andreas Ebner
parent
2810e1ec1b
commit
461e46182c
+4
-3
@@ -6,9 +6,10 @@ refactorings and bug fixes are not reported here.
|
||||
### Removing a PublishedDataSet removes the connected DataSetWriters
|
||||
|
||||
`UA_Server_removePublishedDataSet` removes the connected DataSetWriters together
|
||||
with the PublishedDataSet. While the WriterGroup of a connected writer is
|
||||
enabled, it returns `Bad_InvalidState` and changes nothing. Before, the
|
||||
PublishedDataSet was freed even when removing a connected writer failed.
|
||||
with the PublishedDataSet. If a connected writer has frozen the configuration,
|
||||
it returns `Bad_ConfigurationError`. Otherwise, while the WriterGroup of a
|
||||
connected writer is enabled, it returns `Bad_InvalidState` and changes nothing.
|
||||
Before, the PublishedDataSet was freed even when removing a connected writer failed.
|
||||
|
||||
### PubSub message security with OpenSSL and LibreSSL
|
||||
|
||||
|
||||
@@ -631,6 +631,9 @@ UA_Server_getWriterGroupLastPublishTimestamp(UA_Server *server,
|
||||
const UA_NodeId wgId,
|
||||
UA_DateTime *timestamp);
|
||||
|
||||
/* Remove the group and its DataSetWriters. A failed child removal is returned
|
||||
* and leaves the group pending deletion; retry after resolving the failure.
|
||||
* New writers cannot be added once group deletion has started. */
|
||||
UA_EXPORT UA_StatusCode UA_THREADSAFE
|
||||
UA_Server_removeWriterGroup(UA_Server *server, const UA_NodeId wgId);
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
* Copyright (c) 2021 Fraunhofer IOSB (Author: Jan Hermes)
|
||||
* Copyright (c) 2022 Siemens AG (Author: Thomas Fischer)
|
||||
* Copyright (c) 2022 Fraunhofer IOSB (Author: Noel Graf)
|
||||
* Copyright 2026 (c) o6 Automation GmbH (Author: Andreas Ebner)
|
||||
*/
|
||||
|
||||
#include "ua_pubsub_internal.h"
|
||||
@@ -168,9 +169,10 @@ UA_StatusCode
|
||||
UA_PubSubConnection_delete(UA_PubSubManager *psm, UA_PubSubConnection *c) {
|
||||
UA_LOCK_ASSERT(&psm->drv.server->serviceMutex);
|
||||
|
||||
/* Check with the application if we can remove */
|
||||
/* Check with the application if we can remove. A deferred deletion calls
|
||||
* this again when the channels are closed. Ask only on the first pass. */
|
||||
UA_Server *server = psm->drv.server;
|
||||
if(server->config.pubSubConfig.componentLifecycleCallback) {
|
||||
if(!c->deleteFlag && server->config.pubSubConfig.componentLifecycleCallback) {
|
||||
UA_StatusCode res = server->config.pubSubConfig.
|
||||
componentLifecycleCallback(server, c->head.identifier,
|
||||
UA_PUBSUBCOMPONENT_CONNECTION, true);
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
* Copyright (c) 2020 Yannick Wallerer, Siemens AG
|
||||
* Copyright (c) 2020 Thomas Fischer, Siemens AG
|
||||
* Copyright (c) 2021 Fraunhofer IOSB (Author: Jan Hermes)
|
||||
* Copyright 2025 (c) o6 Automation GmbH (Author: Andreas Ebner)
|
||||
* Copyright 2025-2026 (c) o6 Automation GmbH (Author: Andreas Ebner)
|
||||
* Copyright 2025 (c) o6 Automation GmbH (Author: Julius Pfrommer)
|
||||
*/
|
||||
|
||||
@@ -177,6 +177,11 @@ UA_DataSetWriter_create(UA_PubSubManager *psm,
|
||||
if(!wg)
|
||||
return UA_STATUSCODE_BADNOTFOUND;
|
||||
|
||||
/* A disabled group may still be waiting for its transport channel to close
|
||||
* or for an existing writer's removal to be approved. */
|
||||
if(wg->deleteFlag)
|
||||
return UA_STATUSCODE_BADINVALIDSTATE;
|
||||
|
||||
/* Make checks for a heartbeat */
|
||||
if(UA_NodeId_isNull(&dataSet) && dswConfig->keyFrameCount != 1) {
|
||||
UA_LOG_WARNING_PUBSUB(psm->logging, wg,
|
||||
@@ -331,7 +336,13 @@ UA_DataSetWriter_create(UA_PubSubManager *psm,
|
||||
|
||||
/* Add to the information model */
|
||||
#ifdef UA_ENABLE_PUBSUB_INFORMATIONMODEL
|
||||
res |= addDataSetWriterRepresentation(psm->drv.server, dsw);
|
||||
res = addDataSetWriterRepresentation(psm->drv.server, dsw);
|
||||
if(res != UA_STATUSCODE_GOOD) {
|
||||
/* Do not leave a linked writer behind for a failed creation */
|
||||
UA_PubSubComponent_freeWithoutLifecycleCallback(
|
||||
psm, dsw, UA_PUBSUBCOMPONENT_DATASETWRITER);
|
||||
return res;
|
||||
}
|
||||
#else
|
||||
UA_PubSubManager_generateUniqueNodeId(psm, &dsw->head.identifier);
|
||||
#endif
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
* Copyright (c) 2020 Thomas Fischer, Siemens AG
|
||||
* Copyright (c) 2021 Fraunhofer IOSB (Author: Jan Hermes)
|
||||
* Copyright (c) 2022 Linutronix GmbH (Author: Muddasir Shakil)
|
||||
* Copyright 2025 (c) o6 Automation GmbH (Author: Andreas Ebner)
|
||||
* Copyright 2025-2026 (c) o6 Automation GmbH (Author: Andreas Ebner)
|
||||
* Copyright 2025 (c) o6 Automation GmbH (Author: Julius Pfrommer)
|
||||
*/
|
||||
|
||||
@@ -263,9 +263,13 @@ UA_StatusCode
|
||||
UA_WriterGroup_remove(UA_PubSubManager *psm, UA_WriterGroup *wg) {
|
||||
UA_LOCK_ASSERT(&psm->drv.server->serviceMutex);
|
||||
|
||||
/* Check with the application if we can remove */
|
||||
UA_PubSubConnection *connection = wg->linkedConnection;
|
||||
UA_assert(connection);
|
||||
|
||||
/* Ask only once for the group. Remaining writers still need to be removed
|
||||
* when a channel-close callback or an explicit retry resumes deletion. */
|
||||
UA_Server *server = psm->drv.server;
|
||||
if(server->config.pubSubConfig.componentLifecycleCallback) {
|
||||
if(!wg->deleteFlag && server->config.pubSubConfig.componentLifecycleCallback) {
|
||||
UA_StatusCode res = server->config.pubSubConfig.
|
||||
componentLifecycleCallback(server, wg->head.identifier,
|
||||
UA_PUBSUBCOMPONENT_WRITERGROUP, true);
|
||||
@@ -273,18 +277,21 @@ UA_WriterGroup_remove(UA_PubSubManager *psm, UA_WriterGroup *wg) {
|
||||
return res;
|
||||
}
|
||||
|
||||
UA_PubSubConnection *connection = wg->linkedConnection;
|
||||
UA_assert(connection);
|
||||
|
||||
/* Disable (and disconnect) and set the deleteFlag. This prevents a
|
||||
* reconnect and triggers the deletion when the last open socket is
|
||||
* closed. */
|
||||
wg->deleteFlag = true;
|
||||
UA_WriterGroup_setPubSubState(psm, wg, UA_PUBSUBSTATE_DISABLED);
|
||||
UA_StatusCode res = UA_WriterGroup_setPubSubState(psm, wg, UA_PUBSUBSTATE_DISABLED);
|
||||
if(res != UA_STATUSCODE_GOOD)
|
||||
return res;
|
||||
|
||||
/* A veto must leave the group linked so the writer remains reachable and
|
||||
* can release its PublishedDataSet's configuration freeze on a later retry. */
|
||||
UA_DataSetWriter *dsw, *dsw_tmp;
|
||||
LIST_FOREACH_SAFE(dsw, &wg->writers, listEntry, dsw_tmp) {
|
||||
UA_DataSetWriter_remove(psm, dsw);
|
||||
res = UA_DataSetWriter_remove(psm, dsw);
|
||||
if(res != UA_STATUSCODE_GOOD)
|
||||
return res;
|
||||
}
|
||||
|
||||
if(wg->config.securityPolicy && wg->securityPolicyContext) {
|
||||
@@ -1373,6 +1380,9 @@ WriterGroupChannelCallback(UA_ConnectionManager *cm, uintptr_t connectionId,
|
||||
/* PSC marked for deletion and the last EventLoop connection has closed */
|
||||
if(wg->deleteFlag) {
|
||||
UA_WriterGroup_remove(psm, wg);
|
||||
/* The last socket is closed even if a child veto kept the
|
||||
* group alive. Allow server shutdown to complete. */
|
||||
UA_PubSubManager_setState(psm, psm->drv.state);
|
||||
unlockServer(server);
|
||||
return;
|
||||
}
|
||||
@@ -1639,7 +1649,7 @@ UA_Server_removeWriterGroup(UA_Server *server, const UA_NodeId writerGroup) {
|
||||
UA_PubSubManager *psm = getPSM(server);
|
||||
UA_WriterGroup *wg = UA_WriterGroup_find(psm, writerGroup);
|
||||
if(wg)
|
||||
UA_WriterGroup_remove(psm, wg);
|
||||
res = UA_WriterGroup_remove(psm, wg);
|
||||
else
|
||||
res = UA_STATUSCODE_BADNOTFOUND;
|
||||
unlockServer(server);
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
#include "ua_server_internal.h"
|
||||
#include "ua_pubsub_internal.h"
|
||||
#include "test_helpers.h"
|
||||
#include "testing_clock.h"
|
||||
|
||||
#include <check.h>
|
||||
#include <stdlib.h>
|
||||
@@ -455,6 +456,211 @@ START_TEST(RemovePdsRemovesWritersInDisabledGroups) {
|
||||
UA_PublishedDataSet_find(psm, pdsB));
|
||||
} END_TEST
|
||||
|
||||
static UA_NodeId
|
||||
addTestUnicastWriterGroup(UA_NodeId connId) {
|
||||
UA_WriterGroupConfig wgc;
|
||||
memset(&wgc, 0, sizeof(wgc));
|
||||
wgc.name = UA_STRING("WG-Unicast-Remove");
|
||||
wgc.writerGroupId = 1;
|
||||
wgc.publishingInterval = 100;
|
||||
wgc.encodingMimeType = UA_PUBSUB_ENCODING_UADP;
|
||||
UA_DatagramWriterGroupTransport2DataType transport;
|
||||
memset(&transport, 0, sizeof(transport));
|
||||
UA_NetworkAddressUrlDataType address =
|
||||
{UA_STRING_NULL, UA_STRING("opc.udp://127.0.0.1:4841/")};
|
||||
UA_ExtensionObject_setValue(&transport.address, &address,
|
||||
&UA_TYPES[UA_TYPES_NETWORKADDRESSURLDATATYPE]);
|
||||
UA_ExtensionObject_setValue(&wgc.transportSettings, &transport,
|
||||
&UA_TYPES[UA_TYPES_DATAGRAMWRITERGROUPTRANSPORT2DATATYPE]);
|
||||
UA_NodeId wgId;
|
||||
UA_StatusCode rv = UA_Server_addWriterGroup(server, connId, &wgc, &wgId);
|
||||
ck_assert_int_eq(rv, UA_STATUSCODE_GOOD);
|
||||
return wgId;
|
||||
}
|
||||
|
||||
static void
|
||||
iteratePubSub(void) {
|
||||
for(size_t i = 0; i < 10; i++) {
|
||||
UA_fakeSleep(10);
|
||||
UA_Server_run_iterate(server, false);
|
||||
}
|
||||
}
|
||||
|
||||
static size_t writerRemovalVetoes;
|
||||
static size_t writerRemovalNotifications;
|
||||
static size_t groupRemovalNotifications;
|
||||
|
||||
static UA_StatusCode
|
||||
countAndVetoWriterRemoval(UA_Server *s, const UA_NodeId id,
|
||||
const UA_PubSubComponentType componentType,
|
||||
UA_Boolean remove) {
|
||||
if(!remove)
|
||||
return UA_STATUSCODE_GOOD;
|
||||
if(componentType == UA_PUBSUBCOMPONENT_WRITERGROUP)
|
||||
groupRemovalNotifications++;
|
||||
if(componentType == UA_PUBSUBCOMPONENT_DATASETWRITER) {
|
||||
writerRemovalNotifications++;
|
||||
if(writerRemovalVetoes > 0) {
|
||||
writerRemovalVetoes--;
|
||||
return UA_STATUSCODE_BADUSERACCESSDENIED;
|
||||
}
|
||||
}
|
||||
return UA_STATUSCODE_GOOD;
|
||||
}
|
||||
|
||||
static void
|
||||
setWriterRemovalVetoes(size_t count) {
|
||||
writerRemovalVetoes = count;
|
||||
writerRemovalNotifications = 0;
|
||||
groupRemovalNotifications = 0;
|
||||
UA_Server_getConfig(server)->pubSubConfig.componentLifecycleCallback =
|
||||
countAndVetoWriterRemoval;
|
||||
}
|
||||
|
||||
/* The group has its own socket, so closing it really resumes removal. A
|
||||
* previously vetoed writer must be removed without asking about the group again. */
|
||||
START_TEST(DeferredWriterGroupDeleteRetriesVetoedWriter) {
|
||||
UA_PubSubManager *psm = getPSM(server);
|
||||
UA_NodeId pdsId = addTestPds("PDS-Deferred-Veto");
|
||||
UA_NodeId connId = addTestConnection();
|
||||
UA_NodeId wgId = addTestUnicastWriterGroup(connId);
|
||||
UA_NodeId dswId = addTestWriter(wgId, pdsId, "DSW-Deferred-Veto", 1);
|
||||
ck_assert_int_eq(UA_Server_enableAllPubSubComponents(server), UA_STATUSCODE_GOOD);
|
||||
iteratePubSub();
|
||||
UA_WriterGroup *wg = UA_WriterGroup_find(psm, wgId);
|
||||
UA_PublishedDataSet *pds = UA_PublishedDataSet_find(psm, pdsId);
|
||||
ck_assert(wg->sendChannel != 0);
|
||||
ck_assert_uint_eq(pds->configurationFreezeCounter, 1);
|
||||
|
||||
setWriterRemovalVetoes(1);
|
||||
ck_assert_int_eq(UA_Server_removeWriterGroup(server, wgId),
|
||||
UA_STATUSCODE_BADUSERACCESSDENIED);
|
||||
ck_assert(wg->deleteFlag && wg->sendChannel != 0);
|
||||
ck_assert_uint_eq(wg->writersCount, 1);
|
||||
iteratePubSub();
|
||||
ck_assert_ptr_null(UA_WriterGroup_find(psm, wgId));
|
||||
ck_assert_ptr_null(UA_DataSetWriter_find(psm, dswId));
|
||||
ck_assert_uint_eq(groupRemovalNotifications, 1);
|
||||
ck_assert_uint_eq(writerRemovalNotifications, 2);
|
||||
ck_assert_uint_eq(pds->configurationFreezeCounter, 0);
|
||||
ck_assert_int_eq(UA_Server_removePublishedDataSet(server, pdsId), UA_STATUSCODE_GOOD);
|
||||
UA_Server_getConfig(server)->pubSubConfig.componentLifecycleCallback = NULL;
|
||||
} END_TEST
|
||||
|
||||
/* A persistent child veto must retain both parent and child, with or without a
|
||||
* deferred channel close. An explicit retry completes the approved deletion. */
|
||||
START_TEST(WriterGroupDeletePreservesVetoedWriter) {
|
||||
UA_PubSubManager *psm = getPSM(server);
|
||||
UA_NodeId pdsId = addTestPds("PDS-Persistent-Veto");
|
||||
UA_NodeId connId = addTestConnection();
|
||||
UA_NodeId wgId = (_i == 0) ? addTestWriterGroup(connId, "WG-Veto", 1) :
|
||||
addTestUnicastWriterGroup(connId);
|
||||
UA_NodeId dswId = addTestWriter(wgId, pdsId, "DSW-Persistent-Veto", 1);
|
||||
ck_assert_int_eq(UA_Server_enableAllPubSubComponents(server), UA_STATUSCODE_GOOD);
|
||||
iteratePubSub();
|
||||
UA_WriterGroup *wg = UA_WriterGroup_find(psm, wgId);
|
||||
if(_i != 0)
|
||||
ck_assert(wg->sendChannel != 0);
|
||||
setWriterRemovalVetoes(100);
|
||||
ck_assert_int_eq(UA_Server_removeWriterGroup(server, wgId),
|
||||
UA_STATUSCODE_BADUSERACCESSDENIED);
|
||||
iteratePubSub();
|
||||
ck_assert_ptr_eq(UA_WriterGroup_find(psm, wgId), wg);
|
||||
ck_assert(wg->deleteFlag && wg->sendChannel == 0);
|
||||
ck_assert_uint_eq(wg->writersCount, 1);
|
||||
UA_DataSetWriter *dsw = UA_DataSetWriter_find(psm, dswId);
|
||||
ck_assert_ptr_nonnull(dsw);
|
||||
ck_assert_ptr_eq(dsw->linkedWriterGroup, wg);
|
||||
UA_PublishedDataSet *pds = UA_PublishedDataSet_find(psm, pdsId);
|
||||
ck_assert_uint_eq(pds->configurationFreezeCounter, 1);
|
||||
ck_assert_int_eq(UA_Server_removeWriterGroup(server, wgId),
|
||||
UA_STATUSCODE_BADUSERACCESSDENIED);
|
||||
ck_assert_ptr_eq(UA_WriterGroup_find(psm, wgId), wg);
|
||||
|
||||
writerRemovalVetoes = 0;
|
||||
ck_assert_int_eq(UA_Server_removeWriterGroup(server, wgId), UA_STATUSCODE_GOOD);
|
||||
ck_assert_ptr_null(UA_WriterGroup_find(psm, wgId));
|
||||
ck_assert_ptr_null(UA_DataSetWriter_find(psm, dswId));
|
||||
ck_assert_uint_eq(groupRemovalNotifications, 1);
|
||||
ck_assert_uint_eq(pds->configurationFreezeCounter, 0);
|
||||
ck_assert_int_eq(UA_Server_removePublishedDataSet(server, pdsId), UA_STATUSCODE_GOOD);
|
||||
UA_Server_getConfig(server)->pubSubConfig.componentLifecycleCallback = NULL;
|
||||
} END_TEST
|
||||
|
||||
START_TEST(DeferredWriterGroupDeleteRejectsNewWriters) {
|
||||
UA_PubSubManager *psm = getPSM(server);
|
||||
UA_NodeId pdsId = addTestPds("PDS-Late-Writer");
|
||||
UA_NodeId connId = addTestConnection();
|
||||
UA_NodeId wgId = addTestUnicastWriterGroup(connId);
|
||||
ck_assert_int_eq(UA_Server_enableAllPubSubComponents(server), UA_STATUSCODE_GOOD);
|
||||
iteratePubSub();
|
||||
UA_WriterGroup *wg = UA_WriterGroup_find(psm, wgId);
|
||||
ck_assert(wg->sendChannel != 0);
|
||||
setWriterRemovalVetoes(0);
|
||||
ck_assert_int_eq(UA_Server_removeWriterGroup(server, wgId), UA_STATUSCODE_GOOD);
|
||||
ck_assert(wg->deleteFlag && wg->sendChannel != 0);
|
||||
|
||||
UA_DataSetWriterConfig dswc;
|
||||
memset(&dswc, 0, sizeof(dswc));
|
||||
dswc.name = UA_STRING("DSW-Late");
|
||||
dswc.dataSetWriterId = 1;
|
||||
ck_assert_int_eq(UA_Server_addDataSetWriter(server, wgId, pdsId, &dswc, NULL),
|
||||
UA_STATUSCODE_BADINVALIDSTATE);
|
||||
ck_assert_uint_eq(wg->writersCount, 0);
|
||||
ck_assert_uint_eq(UA_PublishedDataSet_find(psm, pdsId)->configurationFreezeCounter, 0);
|
||||
ck_assert_int_eq(UA_Server_removeWriterGroup(server, wgId), UA_STATUSCODE_GOOD);
|
||||
iteratePubSub();
|
||||
ck_assert_ptr_null(UA_WriterGroup_find(psm, wgId));
|
||||
ck_assert_uint_eq(groupRemovalNotifications, 1);
|
||||
ck_assert_uint_eq(writerRemovalNotifications, 0);
|
||||
ck_assert_int_eq(UA_Server_removePublishedDataSet(server, pdsId), UA_STATUSCODE_GOOD);
|
||||
UA_Server_getConfig(server)->pubSubConfig.componentLifecycleCallback = NULL;
|
||||
} END_TEST
|
||||
|
||||
/* A retained group has no open socket after its close callback. It must not
|
||||
* prevent the PubSubManager from completing a stop request. */
|
||||
START_TEST(DeferredWriterGroupVetoCompletesManagerStop) {
|
||||
UA_PubSubManager *psm = getPSM(server);
|
||||
UA_PubSubConnectionConfig cc;
|
||||
memset(&cc, 0, sizeof(cc));
|
||||
cc.name = UA_STRING("Conn-Unicast-Only");
|
||||
cc.transportProfileUri =
|
||||
UA_STRING("http://opcfoundation.org/UA-Profile/Transport/pubsub-udp-uadp");
|
||||
UA_NetworkAddressUrlDataType address =
|
||||
{UA_STRING_NULL, UA_STRING("opc.udp://localhost:4840/")};
|
||||
UA_Variant_setScalar(&cc.address, &address,
|
||||
&UA_TYPES[UA_TYPES_NETWORKADDRESSURLDATATYPE]);
|
||||
UA_NodeId connId;
|
||||
ck_assert_int_eq(UA_Server_addPubSubConnection(server, &cc, &connId), UA_STATUSCODE_GOOD);
|
||||
UA_NodeId pdsId = addTestPds("PDS-Stop-Veto");
|
||||
UA_NodeId wgId = addTestUnicastWriterGroup(connId);
|
||||
addTestWriter(wgId, pdsId, "DSW-Stop-Veto", 1);
|
||||
ck_assert_int_eq(UA_Server_enableAllPubSubComponents(server), UA_STATUSCODE_GOOD);
|
||||
iteratePubSub();
|
||||
UA_WriterGroup *wg = UA_WriterGroup_find(psm, wgId);
|
||||
ck_assert(wg->sendChannel != 0);
|
||||
UA_PubSubConnection *conn = UA_PubSubConnection_find(psm, connId);
|
||||
ck_assert_uint_eq(conn->sendChannel, 0);
|
||||
ck_assert_uint_eq(conn->recvChannelsSize, 0);
|
||||
|
||||
setWriterRemovalVetoes(100);
|
||||
ck_assert_int_eq(UA_Server_removeWriterGroup(server, wgId),
|
||||
UA_STATUSCODE_BADUSERACCESSDENIED);
|
||||
lockServer(server);
|
||||
UA_PubSubManager_setState(psm, UA_LIFECYCLESTATE_STOPPED);
|
||||
unlockServer(server);
|
||||
ck_assert_int_eq(psm->drv.state, UA_LIFECYCLESTATE_STOPPING);
|
||||
iteratePubSub();
|
||||
ck_assert_ptr_eq(UA_WriterGroup_find(psm, wgId), wg);
|
||||
ck_assert_uint_eq(wg->sendChannel, 0);
|
||||
ck_assert_int_eq(psm->drv.state, UA_LIFECYCLESTATE_STOPPED);
|
||||
|
||||
writerRemovalVetoes = 0;
|
||||
ck_assert_int_eq(UA_Server_removeWriterGroup(server, wgId), UA_STATUSCODE_GOOD);
|
||||
ck_assert_int_eq(UA_Server_removePublishedDataSet(server, pdsId), UA_STATUSCODE_GOOD);
|
||||
UA_Server_getConfig(server)->pubSubConfig.componentLifecycleCallback = NULL;
|
||||
} END_TEST
|
||||
|
||||
START_TEST(GetPublishedDataSetConfigInvalidArgs) {
|
||||
/* unknown id */
|
||||
UA_PublishedDataSetConfig copy;
|
||||
@@ -507,6 +713,10 @@ int main(void) {
|
||||
tcase_add_test(tc_pds_extra, RemovePdsWithWriterInEnabledGroupFails);
|
||||
tcase_add_test(tc_pds_extra, RemovePdsStopsOnVetoedWriterRemoval);
|
||||
tcase_add_test(tc_pds_extra, RemovePdsRemovesWritersInDisabledGroups);
|
||||
tcase_add_test(tc_pds_extra, DeferredWriterGroupDeleteRetriesVetoedWriter);
|
||||
tcase_add_loop_test(tc_pds_extra, WriterGroupDeletePreservesVetoedWriter, 0, 2);
|
||||
tcase_add_test(tc_pds_extra, DeferredWriterGroupDeleteRejectsNewWriters);
|
||||
tcase_add_test(tc_pds_extra, DeferredWriterGroupVetoCompletesManagerStop);
|
||||
|
||||
Suite *s = suite_create("PubSub PublishedDataSets handling");
|
||||
suite_add_tcase(s, tc_add_pubsub_pds_minimal_config);
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*
|
||||
* Copyright (c) 2024 open62541 contributors.
|
||||
* Copyright 2026 (c) o6 Automation GmbH (Author: Andreas Ebner)
|
||||
*
|
||||
* Unit tests that exercise code paths in the PubSub state machines that are
|
||||
* not covered by the existing test suite. The targeted branches are the ones
|
||||
@@ -21,6 +22,7 @@
|
||||
* addReaderGroup/addWriterGroup.
|
||||
* - Some null-argument and unknown-connection paths in the public
|
||||
* addReaderGroup / addWriterGroup wrappers.
|
||||
* - A deferred connection delete asks the componentLifecycleCallback once.
|
||||
*/
|
||||
|
||||
#include <open62541/server.h>
|
||||
@@ -31,6 +33,7 @@
|
||||
#include "ua_server_internal.h"
|
||||
|
||||
#include "test_helpers.h"
|
||||
#include "testing_clock.h"
|
||||
|
||||
#include <check.h>
|
||||
#include <stdlib.h>
|
||||
@@ -459,6 +462,47 @@ START_TEST(RemoveReaderGroup_UnknownReturnsBadNotFound) {
|
||||
ck_assert_int_eq(res, UA_STATUSCODE_BADNOTFOUND);
|
||||
} END_TEST
|
||||
|
||||
/* Removal notifications per component type */
|
||||
static size_t removeNotifications[8];
|
||||
|
||||
static UA_StatusCode
|
||||
removeCountingCallback(UA_Server *s, const UA_NodeId id,
|
||||
const UA_PubSubComponentType componentType,
|
||||
UA_Boolean remove) {
|
||||
if(remove && (size_t)componentType < 8)
|
||||
removeNotifications[componentType]++;
|
||||
return UA_STATUSCODE_GOOD;
|
||||
}
|
||||
|
||||
/* A deferred delete (sockets still open) completes in later EventLoop
|
||||
* iterations. The application is asked only once per component. */
|
||||
START_TEST(DeferredDeleteNotifiesOnce) {
|
||||
UA_Server_run_startup(server);
|
||||
addMinimalPubSubConnection();
|
||||
addMinimalWriterGroup();
|
||||
UA_StatusCode res = UA_Server_enableAllPubSubComponents(server);
|
||||
ck_assert_int_eq(res, UA_STATUSCODE_GOOD);
|
||||
for(size_t i = 0; i < 5; i++) {
|
||||
UA_fakeSleep(50);
|
||||
UA_Server_run_iterate(server, false);
|
||||
}
|
||||
|
||||
UA_PubSubManager *psm = getPSM(server);
|
||||
memset(removeNotifications, 0, sizeof(removeNotifications));
|
||||
UA_ServerConfig *sc = UA_Server_getConfig(server);
|
||||
sc->pubSubConfig.componentLifecycleCallback = removeCountingCallback;
|
||||
res = UA_Server_removePubSubConnection(server, connectionIdent);
|
||||
ck_assert_int_eq(res, UA_STATUSCODE_GOOD);
|
||||
for(size_t i = 0; i < 100 && psm->connectionsSize > 0; i++) {
|
||||
UA_fakeSleep(50);
|
||||
UA_Server_run_iterate(server, false);
|
||||
}
|
||||
sc->pubSubConfig.componentLifecycleCallback = NULL;
|
||||
ck_assert_uint_eq(psm->connectionsSize, 0);
|
||||
ck_assert_uint_eq(removeNotifications[UA_PUBSUBCOMPONENT_CONNECTION], 1);
|
||||
ck_assert_uint_eq(removeNotifications[UA_PUBSUBCOMPONENT_WRITERGROUP], 1);
|
||||
} END_TEST
|
||||
|
||||
static Suite *
|
||||
stateMachineSuite(void) {
|
||||
Suite *s = suite_create("PubSub state machine edge cases");
|
||||
@@ -487,6 +531,7 @@ stateMachineSuite(void) {
|
||||
tcase_add_test(tc_api, RemovePubSubConnection_UnknownReturnsBadNotFound);
|
||||
tcase_add_test(tc_api, RemoveWriterGroup_UnknownReturnsBadNotFound);
|
||||
tcase_add_test(tc_api, RemoveReaderGroup_UnknownReturnsBadNotFound);
|
||||
tcase_add_test(tc_api, DeferredDeleteNotifiesOnce);
|
||||
|
||||
suite_add_tcase(s, tc_wg_state);
|
||||
suite_add_tcase(s, tc_rg_state);
|
||||
|
||||
Reference in New Issue
Block a user