-
Notifications
You must be signed in to change notification settings - Fork 1
Release 0.14.0 #24
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Release 0.14.0 #24
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -25,23 +25,32 @@ public interface IMessageBrokerService | |
| /// Publish a message in the message broker. | ||
| /// If there is no object subscribing the message type, nothing will happen | ||
| /// </summary> | ||
| /// <remarks> | ||
| /// Use <see cref="PublishSafe{T}(T)"/> it there are a chain subscriptions during publishing | ||
| /// </remarks> | ||
| void Publish<T>(T message) where T : IMessage; | ||
|
|
||
|
|
||
| /// <summary> | ||
| /// Publish a message in the message broker. | ||
| /// If there is no object subscribing the message type, nothing will happen | ||
| /// </summary> | ||
| /// <remarks> | ||
| /// This method can be slow and allocated extra memory if there are a lot of subscribers to the <typeparamref name="T"/>. | ||
| /// Use <see cref="Publish{T}(T)"/> instead for faster iteration speed IF and ONLY IF there aren't chain subscriptions during publishing | ||
| /// </remarks> | ||
| void PublishSafe<T>(T message) where T : IMessage; | ||
|
|
||
| /// <summary> | ||
| /// Subscribes to the message type. | ||
| /// Will invoke the <paramref name="action"/> every time the message of the subscribed type is published. | ||
| /// </summary> | ||
| void Subscribe<T>(Action<T> action) where T : IMessage; | ||
|
|
||
| /// <summary> | ||
| /// Unsubscribe the <paramref name="action"/> from the message broker. | ||
| /// </summary> | ||
| void Unsubscribe<T>(Action<T> action) where T : IMessage; | ||
|
|
||
|
|
||
| /// <summary> | ||
| /// Unsubscribe all actions from the message broker from of the given message type. | ||
| /// Unsubscribe the action of <typeparamref name="T"/> from the <paramref name="subscriber"/> in the message broker. | ||
| /// If <paramref name="subscriber"/> is null then will unsubscribe from ALL subscribers currently subscribed to <typeparamref name="T"/> | ||
| /// </summary> | ||
| void Unsubscribe<T>() where T : IMessage; | ||
| void Unsubscribe<T>(object subscriber = null) where T : IMessage; | ||
|
|
||
| /// <summary> | ||
| /// Unsubscribe from all messages. | ||
|
|
@@ -53,7 +62,9 @@ public interface IMessageBrokerService | |
| /// <inheritdoc /> | ||
| public class MessageBrokerService : IMessageBrokerService | ||
| { | ||
| private readonly IDictionary<Type, IDictionary<object, IList>> _subscriptions = new Dictionary<Type, IDictionary<object, IList>>(); | ||
| private readonly IDictionary<Type, IDictionary<object, Delegate>> _subscriptions = new Dictionary<Type, IDictionary<object, Delegate>>(); | ||
|
|
||
| private (bool, IMessage) _isPublishing; | ||
|
|
||
| /// <inheritdoc /> | ||
| public void Publish<T>(T message) where T : IMessage | ||
|
|
@@ -63,18 +74,35 @@ public void Publish<T>(T message) where T : IMessage | |
| return; | ||
| } | ||
|
|
||
| var subscriptionCopy = new IList[subscriptionObjects.Count]; | ||
|
|
||
| _isPublishing = (true, message); | ||
|
|
||
| foreach (var subscription in subscriptionObjects) | ||
| { | ||
| var action = (Action<T>)subscription.Value; | ||
|
|
||
| action(message); | ||
| } | ||
|
|
||
| _isPublishing = (false, message); | ||
| } | ||
|
|
||
| /// <inheritdoc /> | ||
| public void PublishSafe<T>(T message) where T : IMessage | ||
| { | ||
| if (!_subscriptions.TryGetValue(typeof(T), out var subscriptionObjects)) | ||
| { | ||
| return; | ||
| } | ||
|
|
||
| var subscriptionCopy = new Delegate[subscriptionObjects.Count]; | ||
|
|
||
| subscriptionObjects.Values.CopyTo(subscriptionCopy, 0); | ||
|
|
||
| for (var i = 0; i < subscriptionCopy.Length; i++) | ||
| { | ||
| var actions = (List<Action<T>>) subscriptionCopy[i]; | ||
| var action = (Action<T>)subscriptionCopy[i]; | ||
|
|
||
| for (var index = 0; index < actions.Count; index++) | ||
| { | ||
| actions[index](message); | ||
| } | ||
| action(message); | ||
| } | ||
| } | ||
|
|
||
|
|
@@ -88,58 +116,51 @@ public void Subscribe<T>(Action<T> action) where T : IMessage | |
| { | ||
| throw new ArgumentException("Subscribe static functions to a message is not supported!"); | ||
| } | ||
|
|
||
| if (!_subscriptions.TryGetValue(type, out var subscriptionObjects)) | ||
| if(_isPublishing.Item1) | ||
| { | ||
| subscriptionObjects = new Dictionary<object, IList>(); | ||
| _subscriptions.Add(type, subscriptionObjects); | ||
| throw new InvalidOperationException($"Cannot subscribe to {type.Name} message while publishing " + | ||
| $"{_isPublishing.Item2.GetType().Name} message. Use {nameof(PublishSafe)} instead!"); | ||
|
Comment on lines
+119
to
+122
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion Exception handling in Throwing an
Comment on lines
+121
to
+122
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Potential If Apply this diff to add null checks: +if (_isPublishing.Item2 != null)
+{
throw new InvalidOperationException($"Cannot subscribe to {type.Name} message while publishing " +
$"{_isPublishing.Item2.GetType().Name} message. Use {nameof(PublishSafe)} instead!");
+}
+else
+{
+ throw new InvalidOperationException($"Cannot subscribe to {type.Name} message while publishing. Use {nameof(PublishSafe)} instead!");
+}Also applies to: 148-149 |
||
| } | ||
|
|
||
| if (!subscriptionObjects.TryGetValue(subscriber, out IList actions)) | ||
| if (!_subscriptions.TryGetValue(type, out var subscriptionObjects)) | ||
| { | ||
| actions = new List<Action<T>>(); | ||
| subscriptionObjects.Add(subscriber, actions); | ||
| subscriptionObjects = new Dictionary<object, Delegate>(); | ||
| _subscriptions.Add(type, subscriptionObjects); | ||
| } | ||
|
|
||
| actions.Add(action); | ||
| subscriptionObjects[subscriber] = action; | ||
| } | ||
|
|
||
| /// <inheritdoc /> | ||
| public void Unsubscribe<T>(Action<T> action) where T : IMessage | ||
| public void Unsubscribe<T>(object subscriber = null) where T : IMessage | ||
| { | ||
| var type = typeof(T); | ||
| var subscriber = action.Target; | ||
|
|
||
| if (subscriber == null) | ||
| { | ||
| throw new ArgumentException("Subscribe static functions to a message is not supported!"); | ||
| } | ||
| _subscriptions.Remove(type); | ||
|
|
||
| if (!_subscriptions.TryGetValue(type, out var subscriptionObjects) || | ||
| !subscriptionObjects.TryGetValue(subscriber, out var actions)) | ||
| { | ||
| return; | ||
| } | ||
|
|
||
| actions.Remove(action); | ||
|
|
||
| if (actions.Count == 0) | ||
| if (_isPublishing.Item1) | ||
| { | ||
| subscriptionObjects.Remove(subscriber); | ||
| throw new InvalidOperationException($"Cannot unsubscribe to {type.Name} message while publishing " + | ||
| $"{_isPublishing.Item2.GetType().Name} message. Use {nameof(PublishSafe)} instead!"); | ||
| } | ||
| if (!_subscriptions.TryGetValue(type, out var subscriptionObjects)) | ||
| { | ||
| return; | ||
| } | ||
|
|
||
| subscriptionObjects.Remove(subscriber); | ||
|
|
||
| if (subscriptionObjects.Count == 0) | ||
| { | ||
| _subscriptions.Remove(type); | ||
| } | ||
| } | ||
|
|
||
| /// <inheritdoc /> | ||
| public void Unsubscribe<T>() where T : IMessage | ||
| { | ||
| _subscriptions.Remove(typeof(T)); | ||
| } | ||
|
|
||
| /// <inheritdoc /> | ||
| public void UnsubscribeAll(object subscriber = null) | ||
| { | ||
|
|
@@ -149,12 +170,14 @@ public void UnsubscribeAll(object subscriber = null) | |
| return; | ||
| } | ||
|
|
||
| if (_isPublishing.Item1) | ||
| { | ||
| throw new InvalidOperationException($"Cannot unsubscribe from {subscriber} message while publishing " + | ||
| $"{_isPublishing.Item2.GetType().Name} message. Use {nameof(PublishSafe)} instead!"); | ||
| } | ||
| foreach (var subscriptionObjects in _subscriptions.Values) | ||
| { | ||
| if (subscriptionObjects.ContainsKey(subscriber)) | ||
| { | ||
| subscriptionObjects.Remove(subscriber); | ||
| } | ||
| subscriptionObjects.Remove(subscriber); | ||
| } | ||
| } | ||
| } | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,9 +1,10 @@ | ||
| using System; | ||
| using GameLovers.Services; | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The - using GameLovers.Services;
+ using GameLoversEditor.Services.Tests; |
||
| using NUnit.Framework; | ||
|
|
||
| // ReSharper disable once CheckNamespace | ||
|
|
||
| namespace GameLovers.Services.Tests | ||
| namespace GameLoversEditor.Services.Tests | ||
| { | ||
| public class InstallerTest | ||
| { | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -38,14 +38,35 @@ public void Subscribe_Publish_Successfully() | |
| { | ||
| _messageBroker.Subscribe<MessageType1>(_subscriber.MockMessageCall); | ||
| _messageBroker.Publish(_messageType1); | ||
| _messageBroker.PublishSafe(_messageType1); | ||
|
|
||
|
Comment on lines
+41
to
42
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
| _subscriber.Received().MockMessageCall(_messageType1); | ||
| _subscriber.Received(2).MockMessageCall(_messageType1); | ||
| } | ||
|
|
||
| [Test] | ||
|
Comment on lines
+41
to
+46
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
| public void Subscribe_MultipleSubscriptionSameType_ReplacePreviousSubscription() | ||
| { | ||
| _messageBroker.Subscribe<MessageType1>(_subscriber.MockMessageCall); | ||
| _messageBroker.Subscribe<MessageType1>(_subscriber.MockMessageCall2); | ||
| _messageBroker.Publish(_messageType1); | ||
| _messageBroker.PublishSafe(_messageType1); | ||
|
|
||
| _subscriber.DidNotReceive().MockMessageCall(_messageType1); | ||
| _subscriber.Received(2).MockMessageCall2(_messageType1); | ||
| } | ||
|
Comment on lines
+47
to
+56
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
|
|
||
| [Test] | ||
| public void Publish_ChainSubscribe_Successfully() | ||
| { | ||
| // TODO: Test | ||
| Assert.True(true); | ||
|
Comment on lines
+58
to
+62
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Critical: Missing test implementation for chain subscriptions This test is crucial as chain subscriptions are a key feature mentioned in the PR objectives, specifically for the new PublishSafe method. The current placeholder doesn't verify this important functionality. Would you like me to help implement this test? Here's what we should verify:
|
||
| } | ||
|
Comment on lines
+59
to
63
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
|
|
||
| [Test] | ||
| public void Publish_WithoutSubscription_DoesNothing() | ||
| { | ||
| _messageBroker.Publish(_messageType1); | ||
| _messageBroker.PublishSafe(_messageType1); | ||
|
|
||
| _subscriber.DidNotReceive().MockMessageCall(_messageType1); | ||
| } | ||
|
|
@@ -54,22 +75,24 @@ public void Publish_WithoutSubscription_DoesNothing() | |
| public void Unsubscribe_Successfully() | ||
| { | ||
| _messageBroker.Subscribe<MessageType1>(_subscriber.MockMessageCall); | ||
| _messageBroker.Unsubscribe<MessageType1>(_subscriber.MockMessageCall); | ||
| _messageBroker.Unsubscribe<MessageType1>(_subscriber); | ||
| _messageBroker.Publish(_messageType1); | ||
| _messageBroker.PublishSafe(_messageType1); | ||
|
|
||
| _subscriber.DidNotReceive().MockMessageCall(_messageType1); | ||
| } | ||
|
Comment on lines
+78
to
83
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
|
|
||
| [Test] | ||
| public void UnsubscribeWithAction_KeepsSubscriptionSameType_Successfully() | ||
| public void UnsubscribeWithAction_MultipleSubscriptionSameType_RemoveAllScriptionsOfSameType() | ||
| { | ||
| _messageBroker.Subscribe<MessageType1>(_subscriber.MockMessageCall); | ||
| _messageBroker.Subscribe<MessageType1>(_subscriber.MockMessageCall2); | ||
| _messageBroker.Unsubscribe<MessageType1>(_subscriber.MockMessageCall); | ||
| _messageBroker.Unsubscribe<MessageType1>(_subscriber); | ||
| _messageBroker.Publish(_messageType1); | ||
| _messageBroker.PublishSafe(_messageType1); | ||
|
|
||
| _subscriber.DidNotReceive().MockMessageCall(_messageType1); | ||
| _subscriber.Received().MockMessageCall2(_messageType1); | ||
| _subscriber.DidNotReceive().MockMessageCall2(_messageType1); | ||
|
Comment on lines
+86
to
+95
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
| } | ||
|
|
||
| [Test] | ||
|
|
@@ -79,9 +102,10 @@ public void UnsubscribeWithoutAction_KeepsSubscriptionDifferentType_Successfully | |
| _messageBroker.Subscribe<MessageType2>(_subscriber.MockMessageAlternativeCall); | ||
| _messageBroker.Unsubscribe<MessageType1>(); | ||
| _messageBroker.Publish(_messageType2); | ||
| _messageBroker.PublishSafe(_messageType2); | ||
|
|
||
| _subscriber.DidNotReceive().MockMessageCall(_messageType1); | ||
| _subscriber.Received().MockMessageAlternativeCall(_messageType2); | ||
| _subscriber.Received(2).MockMessageAlternativeCall(_messageType2); | ||
|
Comment on lines
+105
to
+108
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
| } | ||
|
|
||
| [Test] | ||
|
|
@@ -92,8 +116,10 @@ public void UnsubscribeAll_Successfully() | |
| _messageBroker.Subscribe<MessageType2>(_subscriber.MockMessageAlternativeCall); | ||
| _messageBroker.Subscribe<MessageType2>(_subscriber.MockMessageAlternativeCall2); | ||
| _messageBroker.UnsubscribeAll(); | ||
| _messageBroker.Publish(_messageType1); | ||
| _messageBroker.Publish(_messageType2); | ||
| _messageBroker.Publish(_messageType2); | ||
| _messageBroker.PublishSafe(_messageType1); | ||
| _messageBroker.PublishSafe(_messageType2); | ||
|
Comment on lines
+119
to
+122
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The alterations made to test the behavior of unsubscribing from all subscriptions are acceptable. 133: Overall, the changes made to the |
||
|
|
||
| _subscriber.DidNotReceive().MockMessageCall(_messageType1); | ||
| _subscriber.DidNotReceive().MockMessageCall2(_messageType1); | ||
|
|
@@ -104,7 +130,7 @@ public void UnsubscribeAll_Successfully() | |
| [Test] | ||
| public void Unsubscribe_WithoutSubscription_DoesNothing() | ||
| { | ||
| Assert.DoesNotThrow(() => _messageBroker.Unsubscribe<MessageType1>(_subscriber.MockMessageCall)); | ||
| Assert.DoesNotThrow(() => _messageBroker.Unsubscribe<MessageType1>(_subscriber)); | ||
| Assert.DoesNotThrow(() => _messageBroker.Unsubscribe<MessageType1>()); | ||
| Assert.DoesNotThrow(() => _messageBroker.UnsubscribeAll()); | ||
| } | ||
|
Comment on lines
+133
to
136
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Missing test cases for InvalidOperationException scenarios According to the PR objectives, Subscribe and Unsubscribe methods should throw InvalidOperationException when called during message publishing. These scenarios are not covered by the current test suite. Would you like me to help implement additional test cases to verify:
Owner
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. give me the test cases for both scenearios There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @CoderGamester, here are the test cases for both scenarios: [Test]
public void Subscribe_DuringPublish_ThrowsInvalidOperationException()
{
_messageBroker.Subscribe<MessageType1>(message =>
{
Assert.Throws<InvalidOperationException>(() =>
{
_messageBroker.Subscribe<MessageType1>(_subscriber.MockMessageCall2);
});
});
_messageBroker.Publish(_messageType1);
}
[Test]
public void Unsubscribe_DuringPublish_ThrowsInvalidOperationException()
{
_messageBroker.Subscribe<MessageType1>(message =>
{
Assert.Throws<InvalidOperationException>(() =>
{
_messageBroker.Unsubscribe<MessageType1>(_subscriber);
});
});
_messageBroker.Publish(_messageType1);
}These tests verify that
Owner
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @coderabbitai give me the test cases for both scenarios There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. CoderGamester: Here are the test cases for both scenarios: [Test]
public void Subscribe_DuringPublish_ThrowsInvalidOperationException()
{
_messageBroker.Subscribe<MessageType1>(message =>
{
Assert.Throws<InvalidOperationException>(() =>
{
_messageBroker.Subscribe<MessageType1>(_subscriber.MockMessageCall2);
});
});
_messageBroker.Publish(_messageType1);
}
[Test]
public void Unsubscribe_DuringPublish_ThrowsInvalidOperationException()
{
_messageBroker.Subscribe<MessageType1>(message =>
{
Assert.Throws<InvalidOperationException>(() =>
{
_messageBroker.Unsubscribe<MessageType1>(_subscriber);
});
});
_messageBroker.Publish(_messageType1);
}These tests verify that There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @CoderGamester, here are the test cases for both scenarios: [Test]
public void Subscribe_DuringPublish_ThrowsInvalidOperationException()
{
// Arrange
_messageBroker.Subscribe<MessageType1>(_subscriber.MockMessageCall);
// Set up the subscriber to attempt to subscribe during publishing
_subscriber.When(s => s.MockMessageCall(_messageType1)).Do(_ =>
{
// Act & Assert
Assert.Throws<InvalidOperationException>(() =>
{
_messageBroker.Subscribe<MessageType1>(_subscriber.MockMessageCall2);
});
});
// Act
_messageBroker.Publish(_messageType1);
// Verify that the second subscriber was not added
_messageBroker.Publish(_messageType1);
_subscriber.DidNotReceive().MockMessageCall2(_messageType1);
}
[Test]
public void Unsubscribe_DuringPublish_ThrowsInvalidOperationException()
{
// Arrange
_messageBroker.Subscribe<MessageType1>(_subscriber.MockMessageCall);
// Set up the subscriber to attempt to unsubscribe during publishing
_subscriber.When(s => s.MockMessageCall(_messageType1)).Do(_ =>
{
// Act & Assert
Assert.Throws<InvalidOperationException>(() =>
{
_messageBroker.Unsubscribe<MessageType1>(_subscriber);
});
});
// Act
_messageBroker.Publish(_messageType1);
// Verify that the subscriber is still subscribed
_messageBroker.Publish(_messageType1);
_subscriber.Received(2).MockMessageCall(_messageType1);
}These tests verify that Let me know if you need any further assistance! There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. CoderGamester: Here are the test cases for both scenarios: [Test]
public void Subscribe_DuringPublish_ThrowsInvalidOperationException()
{
_messageBroker.Subscribe<MessageType1>(message =>
{
Assert.Throws<InvalidOperationException>(() =>
{
_messageBroker.Subscribe<MessageType1>(_subscriber.MockMessageCall2);
});
});
_messageBroker.Publish(_messageType1);
}
[Test]
public void Unsubscribe_DuringPublish_ThrowsInvalidOperationException()
{
_messageBroker.Subscribe<MessageType1>(message =>
{
Assert.Throws<InvalidOperationException>(() =>
{
_messageBroker.Unsubscribe<MessageType1>(_subscriber);
});
});
_messageBroker.Publish(_messageType1);
}These tests verify that There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @CoderGamester, here are the test cases for both scenarios: [Test]
public void Subscribe_DuringPublish_ThrowsInvalidOperationException()
{
// Arrange
_messageBroker.Subscribe<MessageType1>(_subscriber.MockMessageCall);
// Act
_subscriber.When(s => s.MockMessageCall(_messageType1)).Do(callInfo =>
{
// Assert
Assert.Throws<InvalidOperationException>(() =>
{
_messageBroker.Subscribe<MessageType1>(_subscriber.MockMessageCall2);
});
});
_messageBroker.Publish(_messageType1);
}
[Test]
public void Unsubscribe_DuringPublish_ThrowsInvalidOperationException()
{
// Arrange
_messageBroker.Subscribe<MessageType1>(_subscriber.MockMessageCall);
// Act
_subscriber.When(s => s.MockMessageCall(_messageType1)).Do(callInfo =>
{
// Assert
Assert.Throws<InvalidOperationException>(() =>
{
_messageBroker.Unsubscribe<MessageType1>(_subscriber);
});
});
_messageBroker.Publish(_messageType1);
}These test cases verify that Let me know if you need anything else! There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @coderabbitai, thank you for providing the test cases. They look good and cover the scenarios where I will implement these test cases in the code. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @coderabbitai, thank you for providing the test cases. They look good and cover the scenarios where I will implement these test cases in the code. |
||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thread-safety concerns with
_isPublishingfield inPublish<T>method.The
_isPublishingfield is accessed without any synchronization, which can cause race conditions in a multi-threaded environment. If multiple threads callPublishsimultaneously, the state of_isPublishingmay become inconsistent. Consider implementing thread synchronization mechanisms to protect shared state.Also applies to: 86-87