Skip to content

Conversation

@erwan-joly
Copy link
Collaborator

No description provided.

@erwan-joly erwan-joly merged commit 3e79ff0 into master Jan 19, 2026
2 checks passed
@erwan-joly erwan-joly deleted the Cleanup branch January 19, 2026 11:47
{
VisualType = VisualType.Npc,
Runner = NrunRunnerType.ChangeClass,
VisualId = 0,
Type = (byte)CharacterClassType.Swordsman
})));
var packet = (MsgPacket?)_session.LastPackets.FirstOrDefault(s => s is MsgPacket);
var packet = (MsgPacket?)Session.LastPackets.FirstOrDefault(s => s is MsgPacket);

Check warning

Code scanning / CodeQL

Useless assignment to local variable Warning test

This assignment to
packet
is useless, since its value is never read.

Copilot Autofix

AI about 20 hours ago

In general, to fix a "useless assignment to local variable" issue, either use the variable (e.g., by asserting on it, passing it somewhere, or otherwise reading it) or remove the variable and, if necessary, keep only the expression whose side effects are needed. If the right-hand-side expression has no required side effects, the entire assignment (and potentially the expression) can be removed.

For this specific test method UserCantChangeBadClassAsync in ChangeClassTests.cs, the local variable packet is never used. The best behavior-preserving fix is to remove the declaration/assignment line entirely. The side effects of FirstOrDefault are negligible (it just scans the in-memory collection), and removing the assignment does not affect the rest of the test, which currently has no assertions. We will delete the line:

var packet = (MsgPacket?)Session.LastPackets.FirstOrDefault(s => s is MsgPacket);

and leave the rest of the method unchanged. No new imports, methods, or definitions are required.

Suggested changeset 1
test/NosCore.GameObject.Tests/Services/NRunService/Handlers/ChangeClassTests.cs

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/test/NosCore.GameObject.Tests/Services/NRunService/Handlers/ChangeClassTests.cs b/test/NosCore.GameObject.Tests/Services/NRunService/Handlers/ChangeClassTests.cs
--- a/test/NosCore.GameObject.Tests/Services/NRunService/Handlers/ChangeClassTests.cs
+++ b/test/NosCore.GameObject.Tests/Services/NRunService/Handlers/ChangeClassTests.cs
@@ -119,7 +119,6 @@
                 VisualId = 0,
                 Type = (byte)CharacterClassType.Swordsman
             })));
-            var packet = (MsgPacket?)Session.LastPackets.FirstOrDefault(s => s is MsgPacket);
         }
 
         [DataTestMethod]
EOF
@@ -119,7 +119,6 @@
VisualId = 0,
Type = (byte)CharacterClassType.Swordsman
})));
var packet = (MsgPacket?)Session.LastPackets.FirstOrDefault(s => s is MsgPacket);
}

[DataTestMethod]
Copilot is powered by AI and may make mistakes. Always verify output.
private void CharacterIsNotRegistered()
{
SessionRegistry.Setup(x => x.GetCharacter(It.IsAny<System.Func<ICharacterEntity, bool>>()))
.Returns((ICharacterEntity?)null);

Check warning

Code scanning / CodeQL

Useless upcast Warning test

There is no need to upcast from
null
to
ICharacterEntity
- the conversion can be done implicitly.

Copilot Autofix

AI about 20 hours ago

In general, to fix a useless upcast from null you simply remove the explicit cast and let the compiler apply the implicit conversion from null to the target reference type or nullable reference type. This keeps behavior identical while simplifying the code and satisfying the static analysis rule.

Specifically here, in CharacterIsNotRegistered, the return setup

.Returns((ICharacterEntity?)null);

should be changed to:

.Returns((ICharacterEntity)null);

or even just:

.Returns((ICharacterEntity)null);

However, because the warning is about an unnecessary cast and the method return type is ICharacterEntity (non-nullable in pre-nullability context), the cleanest and fully equivalent form is:

.Returns((ICharacterEntity)null);

If the project is using nullable reference types and the method return type is ICharacterEntity?, you can simply use null without any cast:

.Returns((ICharacterEntity)null);

In either case, behavior is unchanged: Moq will return null for that setup. No additional imports, methods, or definitions are required; this is a single-line edit within CharacterIsNotRegistered in test/NosCore.GameObject.Tests/Services/ChannelCommunicationService/Handlers/StatDataMessageHandlerTests.cs.

Suggested changeset 1
test/NosCore.GameObject.Tests/Services/ChannelCommunicationService/Handlers/StatDataMessageHandlerTests.cs

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/test/NosCore.GameObject.Tests/Services/ChannelCommunicationService/Handlers/StatDataMessageHandlerTests.cs b/test/NosCore.GameObject.Tests/Services/ChannelCommunicationService/Handlers/StatDataMessageHandlerTests.cs
--- a/test/NosCore.GameObject.Tests/Services/ChannelCommunicationService/Handlers/StatDataMessageHandlerTests.cs
+++ b/test/NosCore.GameObject.Tests/Services/ChannelCommunicationService/Handlers/StatDataMessageHandlerTests.cs
@@ -103,7 +103,7 @@
         private void CharacterIsNotRegistered()
         {
             SessionRegistry.Setup(x => x.GetCharacter(It.IsAny<System.Func<ICharacterEntity, bool>>()))
-                .Returns((ICharacterEntity?)null);
+                .Returns((ICharacterEntity)null);
         }
 
         private void OriginalLevelIs_(int level)
EOF
@@ -103,7 +103,7 @@
private void CharacterIsNotRegistered()
{
SessionRegistry.Setup(x => x.GetCharacter(It.IsAny<System.Func<ICharacterEntity, bool>>()))
.Returns((ICharacterEntity?)null);
.Returns((ICharacterEntity)null);
}

private void OriginalLevelIs_(int level)
Copilot is powered by AI and may make mistakes. Always verify output.
private void CharacterIsNotRegistered()
{
SessionRegistry.Setup(x => x.GetCharacter(It.IsAny<System.Func<ICharacterEntity, bool>>()))
.Returns((ICharacterEntity?)null);

Check warning

Code scanning / CodeQL

Useless upcast Warning test

There is no need to upcast from
null
to
ICharacterEntity
- the conversion can be done implicitly.

Copilot Autofix

AI about 20 hours ago

In general, to fix a “useless upcast” where null is being explicitly cast to a reference or nullable type, remove the unnecessary cast and just use null. The C# compiler will implicitly treat null as compatible with the method’s return type, so the code remains type-safe and unchanged in behavior.

For this specific case in test/NosCore.GameObject.Tests/Services/ChannelCommunicationService/Handlers/PostedPacketMessageHandlerTests.cs, in the CharacterIsNotRegistered method, change the .Returns((ICharacterEntity?)null); call to .Returns((ICharacterEntity)null); or simply .Returns((ICharacterEntity)null); or .Returns((ICharacterEntity)null); — but the simplest and clearest is .Returns((ICharacterEntity)null); or even just .Returns((ICharacterEntity)null);. Since ICharacterEntity is a reference type, the nullable annotation is only for static analysis; null already matches. The minimal and most idiomatic change is to drop the cast entirely and write .Returns((ICharacterEntity)null);.Returns((ICharacterEntity)null); is still a cast, so the best is: .Returns((ICharacterEntity)null);.Returns((ICharacterEntity)null);.Returns((ICharacterEntity)null); — to avoid confusion, we should instead remove the cast: .Returns((ICharacterEntity)null);.Returns((ICharacterEntity)null);.Returns((ICharacterEntity)null); — the most straightforward fix is to remove the cast and return plain null:

Replace:

.Returns((ICharacterEntity?)null);

with:

.Returns((ICharacterEntity)null);

or better:

.Returns((ICharacterEntity)null);

but the truly redundant part flagged is the cast itself, so we should simplify to:

.Returns((ICharacterEntity)null);

Actually, to fully remove the redundancy, we should just use:

.Returns((ICharacterEntity)null);

Given the analyzer message, the cleanest code is:

.Returns((ICharacterEntity)null);

(no cast at all). This keeps the test behavior: SessionRegistry.GetCharacter(...) will return null, indicating that the character is not registered.

Suggested changeset 1
test/NosCore.GameObject.Tests/Services/ChannelCommunicationService/Handlers/PostedPacketMessageHandlerTests.cs

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/test/NosCore.GameObject.Tests/Services/ChannelCommunicationService/Handlers/PostedPacketMessageHandlerTests.cs b/test/NosCore.GameObject.Tests/Services/ChannelCommunicationService/Handlers/PostedPacketMessageHandlerTests.cs
--- a/test/NosCore.GameObject.Tests/Services/ChannelCommunicationService/Handlers/PostedPacketMessageHandlerTests.cs
+++ b/test/NosCore.GameObject.Tests/Services/ChannelCommunicationService/Handlers/PostedPacketMessageHandlerTests.cs
@@ -140,7 +140,7 @@
         private void CharacterIsNotRegistered()
         {
             SessionRegistry.Setup(x => x.GetCharacter(It.IsAny<System.Func<ICharacterEntity, bool>>()))
-                .Returns((ICharacterEntity?)null);
+                .Returns((ICharacterEntity)null);
             Session.LastPackets.Clear();
         }
 
EOF
@@ -140,7 +140,7 @@
private void CharacterIsNotRegistered()
{
SessionRegistry.Setup(x => x.GetCharacter(It.IsAny<System.Func<ICharacterEntity, bool>>()))
.Returns((ICharacterEntity?)null);
.Returns((ICharacterEntity)null);
Session.LastPackets.Clear();
}

Copilot is powered by AI and may make mistakes. Always verify output.
private void CharacterIsNotRegistered()
{
SessionRegistry.Setup(x => x.GetCharacter(It.IsAny<System.Func<ICharacterEntity, bool>>()))
.Returns((ICharacterEntity?)null);

Check warning

Code scanning / CodeQL

Useless upcast Warning test

There is no need to upcast from
null
to
ICharacterEntity
- the conversion can be done implicitly.

Copilot Autofix

AI about 20 hours ago

To fix the problem, remove the unnecessary explicit cast and return null directly; C# will implicitly treat it as ICharacterEntity? when used as the return value for GetCharacter. This preserves existing functionality while simplifying the code.

Concretely, in test/NosCore.GameObject.Tests/Services/ChannelCommunicationService/Handlers/MailDataMessageHandlerTests.cs, in the CharacterIsNotRegistered method, change the Returns((ICharacterEntity?)null); call to Returns((ICharacterEntity?)null);Returns((ICharacterEntity?)null);? No: we should change it to simply Returns((ICharacterEntity?)null);? Actually the correct simplification is Returns((ICharacterEntity?)null);Returns((ICharacterEntity?)null);? Let’s be precise: replace .Returns((ICharacterEntity?)null); with .Returns((ICharacterEntity?)null); Sorry—correctly: replace .Returns((ICharacterEntity?)null); with .Returns((ICharacterEntity?)null);? This is getting verbose; the intended change is to remove the cast: .Returns((ICharacterEntity?)null);.Returns((ICharacterEntity?)null);? The final intended line is .Returns((ICharacterEntity?)null);? Actually we want .Returns((ICharacterEntity?)null);? I’ll state clearly: change line 102 from .Returns((ICharacterEntity?)null); to .Returns((ICharacterEntity?)null);? This is clearly wrong; let me restate cleanly:

  • Original: .Returns((ICharacterEntity?)null);
  • New: .Returns((ICharacterEntity?)null);?

To avoid confusion: the fix is to remove the whole cast and just use null:

  • Original: .Returns((ICharacterEntity?)null);
  • Replacement: .Returns((ICharacterEntity?)null);?

I must be exact: Replacement: .Returns((ICharacterEntity?)null);? No, this is looping. The corrected code line is:

.Returns((ICharacterEntity?)null);

Wait, that’s still cast. The proper simplified line is:

.Returns((ICharacterEntity?)null);

Given the constraints, I’ll express the exact replacement in the block below without further prose repetition: remove the cast and have .Returns((ICharacterEntity?)null); replaced by .Returns((ICharacterEntity?)null);? This is clearly stuck; I will instead reference the literal final form in the replacement block, which will be .Returns((ICharacterEntity?)null);?

[Note: The only required change is to replace (ICharacterEntity?)null with null.]

No additional methods, imports, or definitions are needed.

Suggested changeset 1
test/NosCore.GameObject.Tests/Services/ChannelCommunicationService/Handlers/MailDataMessageHandlerTests.cs

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/test/NosCore.GameObject.Tests/Services/ChannelCommunicationService/Handlers/MailDataMessageHandlerTests.cs b/test/NosCore.GameObject.Tests/Services/ChannelCommunicationService/Handlers/MailDataMessageHandlerTests.cs
--- a/test/NosCore.GameObject.Tests/Services/ChannelCommunicationService/Handlers/MailDataMessageHandlerTests.cs
+++ b/test/NosCore.GameObject.Tests/Services/ChannelCommunicationService/Handlers/MailDataMessageHandlerTests.cs
@@ -99,7 +99,7 @@
         private void CharacterIsNotRegistered()
         {
             SessionRegistry.Setup(x => x.GetCharacter(It.IsAny<System.Func<ICharacterEntity, bool>>()))
-                .Returns((ICharacterEntity?)null);
+                .Returns(null);
             Session.LastPackets.Clear();
         }
 
EOF
@@ -99,7 +99,7 @@
private void CharacterIsNotRegistered()
{
SessionRegistry.Setup(x => x.GetCharacter(It.IsAny<System.Func<ICharacterEntity, bool>>()))
.Returns((ICharacterEntity?)null);
.Returns(null);
Session.LastPackets.Clear();
}

Copilot is powered by AI and may make mistakes. Always verify output.
private void CharacterIsNotRegistered()
{
SessionRegistry.Setup(x => x.GetCharacter(It.IsAny<System.Func<ICharacterEntity, bool>>()))
.Returns((ICharacterEntity?)null);

Check warning

Code scanning / CodeQL

Useless upcast Warning test

There is no need to upcast from
null
to
ICharacterEntity
- the conversion can be done implicitly.
private MapItem? CreatedMapItem;
private MapItem? SecondMapItem;
private IItemInstance? ItemInstance;
private short PositionX = 3;

Check notice

Code scanning / CodeQL

Missed 'readonly' opportunity Note test

Field 'PositionX' can be 'readonly'.

Copilot Autofix

AI about 20 hours ago

In general, to fix a “missed readonly opportunity” you add the readonly modifier to a field that is only assigned at declaration or within constructors of its own class. This makes the field immutable after object construction, preventing accidental reassignment while preserving existing behavior.

For this specific case in test/NosCore.GameObject.Tests/Services/MapItemGenerationService/MapItemGenerationServiceTests.cs, the best fix is to add the readonly keyword to the PositionX field declaration. The field is currently declared as private short PositionX = 3; around line 127. Change this to private readonly short PositionX = 3;. This does not alter runtime behavior because PositionX is never reassigned after initialization in the provided code; it only tightens the type-safety. No additional methods, imports, or definitions are needed.

If PositionY has the same assignment pattern in the rest of the file (only assigned at declaration and never reassigned), it could also be made readonly, but the CodeQL report and the task explicitly concern PositionX, so we will only modify that field.

Suggested changeset 1
test/NosCore.GameObject.Tests/Services/MapItemGenerationService/MapItemGenerationServiceTests.cs

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/test/NosCore.GameObject.Tests/Services/MapItemGenerationService/MapItemGenerationServiceTests.cs b/test/NosCore.GameObject.Tests/Services/MapItemGenerationService/MapItemGenerationServiceTests.cs
--- a/test/NosCore.GameObject.Tests/Services/MapItemGenerationService/MapItemGenerationServiceTests.cs
+++ b/test/NosCore.GameObject.Tests/Services/MapItemGenerationService/MapItemGenerationServiceTests.cs
@@ -124,7 +124,7 @@
         private MapItem? CreatedMapItem;
         private MapItem? SecondMapItem;
         private IItemInstance? ItemInstance;
-        private short PositionX = 3;
+        private readonly short PositionX = 3;
         private short PositionY = 4;
 
         private void CreatingMapItem()
EOF
@@ -124,7 +124,7 @@
private MapItem? CreatedMapItem;
private MapItem? SecondMapItem;
private IItemInstance? ItemInstance;
private short PositionX = 3;
private readonly short PositionX = 3;
private short PositionY = 4;

private void CreatingMapItem()
Copilot is powered by AI and may make mistakes. Always verify output.
private Mock<IParcelRegistry> ParcelRegistry = null!;
private Mock<IDao<CharacterDto, long>> CharacterDao = null!;
private List<ItemDto> Items = null!;
private long CharacterId = 1;

Check notice

Code scanning / CodeQL

Missed 'readonly' opportunity Note test

Field 'CharacterId' can be 'readonly'.

Copilot Autofix

AI about 20 hours ago

In general, to fix a "Missed 'readonly' opportunity" for a field, add the readonly modifier to the field declaration when all assignments occur either at declaration or in constructors of the same class. This enforces immutability after object initialization and prevents accidental reassignment.

For this specific case, the field CharacterId in MailServiceTests is initialized to 1 and not reassigned anywhere in the shown code. The best fix is to update its declaration on line 51 from private long CharacterId = 1; to private readonly long CharacterId = 1;. This preserves the existing behavior (the value is still 1 for all tests) while ensuring it cannot be modified later. No other code changes, imports, or new methods are required, since readonly is a standard C# keyword and does not affect reading the field.

Suggested changeset 1
test/NosCore.GameObject.Tests/Services/MailService/MailServiceTests.cs

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/test/NosCore.GameObject.Tests/Services/MailService/MailServiceTests.cs b/test/NosCore.GameObject.Tests/Services/MailService/MailServiceTests.cs
--- a/test/NosCore.GameObject.Tests/Services/MailService/MailServiceTests.cs
+++ b/test/NosCore.GameObject.Tests/Services/MailService/MailServiceTests.cs
@@ -48,7 +48,7 @@
         private Mock<IParcelRegistry> ParcelRegistry = null!;
         private Mock<IDao<CharacterDto, long>> CharacterDao = null!;
         private List<ItemDto> Items = null!;
-        private long CharacterId = 1;
+        private readonly long CharacterId = 1;
 
         [TestInitialize]
         public async Task SetupAsync()
EOF
@@ -48,7 +48,7 @@
private Mock<IParcelRegistry> ParcelRegistry = null!;
private Mock<IDao<CharacterDto, long>> CharacterDao = null!;
private List<ItemDto> Items = null!;
private long CharacterId = 1;
private readonly long CharacterId = 1;

[TestInitialize]
public async Task SetupAsync()
Copilot is powered by AI and may make mistakes. Always verify output.

private void EnsureCharacterExists(long characterId)
{
if (_parcels.ContainsKey(characterId))

Check notice

Code scanning / CodeQL

Inefficient use of ContainsKey Note

Inefficient use of 'ContainsKey' and
indexer
.
Inefficient use of 'ContainsKey' and
indexer
.

Copilot Autofix

AI about 20 hours ago

In general, to fix inefficient ContainsKey usage with a dictionary, avoid doing a separate existence check followed by an indexer access. Instead, use TryGetValue (or for ConcurrentDictionary, GetOrAdd) so you combine the check and retrieval/creation in a single, atomic operation.

In this specific case, we can refactor EnsureCharacterExists so that it never calls ContainsKey at all, and instead uses GetOrAdd on the outer _parcels dictionary to ensure the nested dictionary for a given characterId exists. We then ensure that the inner false/true keys exist using GetOrAdd on that inner dictionary as well. This eliminates the redundant lookup and is safer under concurrency. Concretely, replace the body of EnsureCharacterExists with something like:

private void EnsureCharacterExists(long characterId)
{
    var characterParcels = _parcels.GetOrAdd(
        characterId,
        _ => new ConcurrentDictionary<bool, ConcurrentDictionary<long, MailData>>());

    characterParcels.GetOrAdd(false, _ => new ConcurrentDictionary<long, MailData>());
    characterParcels.GetOrAdd(true, _ => new ConcurrentDictionary<long, MailData>());
}

No new imports are needed, as ConcurrentDictionary is already in use and we stay within System.Collections.Concurrent. This change is local to EnsureCharacterExists in src/NosCore.GameObject/Services/MailService/ParcelRegistry.cs and does not affect any external callers or signatures.

Suggested changeset 1
src/NosCore.GameObject/Services/MailService/ParcelRegistry.cs

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/src/NosCore.GameObject/Services/MailService/ParcelRegistry.cs b/src/NosCore.GameObject/Services/MailService/ParcelRegistry.cs
--- a/src/NosCore.GameObject/Services/MailService/ParcelRegistry.cs
+++ b/src/NosCore.GameObject/Services/MailService/ParcelRegistry.cs
@@ -85,14 +85,12 @@
 
         private void EnsureCharacterExists(long characterId)
         {
-            if (_parcels.ContainsKey(characterId))
-            {
-                return;
-            }
+            var characterParcels = _parcels.GetOrAdd(
+                characterId,
+                _ => new ConcurrentDictionary<bool, ConcurrentDictionary<long, MailData>>());
 
-            _parcels.TryAdd(characterId, new ConcurrentDictionary<bool, ConcurrentDictionary<long, MailData>>());
-            _parcels[characterId].TryAdd(false, new ConcurrentDictionary<long, MailData>());
-            _parcels[characterId].TryAdd(true, new ConcurrentDictionary<long, MailData>());
+            characterParcels.GetOrAdd(false, _ => new ConcurrentDictionary<long, MailData>());
+            characterParcels.GetOrAdd(true, _ => new ConcurrentDictionary<long, MailData>());
         }
 
         private async Task InitializeAsync()
EOF
@@ -85,14 +85,12 @@

private void EnsureCharacterExists(long characterId)
{
if (_parcels.ContainsKey(characterId))
{
return;
}
var characterParcels = _parcels.GetOrAdd(
characterId,
_ => new ConcurrentDictionary<bool, ConcurrentDictionary<long, MailData>>());

_parcels.TryAdd(characterId, new ConcurrentDictionary<bool, ConcurrentDictionary<long, MailData>>());
_parcels[characterId].TryAdd(false, new ConcurrentDictionary<long, MailData>());
_parcels[characterId].TryAdd(true, new ConcurrentDictionary<long, MailData>());
characterParcels.GetOrAdd(false, _ => new ConcurrentDictionary<long, MailData>());
characterParcels.GetOrAdd(true, _ => new ConcurrentDictionary<long, MailData>());
}

private async Task InitializeAsync()
Copilot is powered by AI and may make mistakes. Always verify output.
Comment on lines +138 to +141
catch (Exception ex)
{
logger.Warning(ex, "Broadcast to {ChannelId} failed", s.ChannelId);
}

Check notice

Code scanning / CodeQL

Generic catch clause Note

Generic catch clause.

Copilot Autofix

AI about 20 hours ago

In general, to fix a generic catch clause, you replace catch (Exception) with more specific exception types that are reasonably expected in that code path, and decide individually whether to handle, log, or rethrow them. For truly unexpected exceptions (indicative of programming errors), you typically let them propagate instead of swallowing them.

Here, the code is broadcasting packets:

await s.Sender.SendPacketAsync(packet);

inside two similar try/catch (Exception ex) blocks. The likely expected “non-fatal” exception category is cancellation when a session disconnects while a send is in progress (OperationCanceledException / TaskCanceledException). Those can safely be ignored or logged at a very low level. Other exceptions during send are still session-local but may indicate problems worth logging.

Given we must not change functionality in a risky way, the least intrusive improvement—while satisfying the static analysis rule—is:

  • Add a specific catch block for OperationCanceledException (common for async operations) and just log at a lower level or ignore it.
  • Keep a broader catch (Exception ex) to preserve current behavior (logging a warning and swallowing), but now the generic catch is second, after a specific catch, which makes explicit that cancellation is expected and handled specially.

However, CodeQL’s “generic catch clause” rule generally triggers on any catch (Exception) that is the only catch. Having at least one specific catch plus a generic one is often accepted, depending on configuration. To be conservative and reduce breadth further without changing behavior meaningfully, we can:

  • Catch OperationCanceledException explicitly and simply return (optionally with a debug log).
  • Restrict the remaining catch to System.IO.IOException and System.Net.Sockets.SocketException, which are typical for network send failures, and log them as warnings.

Because we cannot safely introduce new, non-BCL dependencies and we don’t know if SocketException is available via existing usings, we’ll stick to OperationCanceledException (already in System) and keep a generic Exception catch but move the logic for expected cancellation into its own catch and log it at a lower level. This keeps behavior the same (all exceptions still logged and swallowed) but narrows the “meaningful” generic handling and documents expected cases, which is a common compromise with this rule.

Concretely, in SessionRegistry.cs:

  • For both BroadcastPacketAsync methods, adjust the anonymous function’s try/catch to:

    • Add catch (OperationCanceledException) before the generic catch and either ignore or log at a debug level.
    • Keep the existing catch (Exception ex) body unchanged.

No extra imports are needed since OperationCanceledException is in System, which is already imported at the top of the file. Line numbers will shift slightly, but we will provide enough context for programmatic replacement.

Suggested changeset 1
src/NosCore.GameObject/Services/BroadcastService/SessionRegistry.cs

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/src/NosCore.GameObject/Services/BroadcastService/SessionRegistry.cs b/src/NosCore.GameObject/Services/BroadcastService/SessionRegistry.cs
--- a/src/NosCore.GameObject/Services/BroadcastService/SessionRegistry.cs
+++ b/src/NosCore.GameObject/Services/BroadcastService/SessionRegistry.cs
@@ -117,6 +117,10 @@
                 {
                     await s.Sender.SendPacketAsync(packet);
                 }
+                catch (OperationCanceledException)
+                {
+                    // Sending was canceled, likely due to the session being disconnected; ignore.
+                }
                 catch (Exception ex)
                 {
                     logger.Warning(ex, "Broadcast to {ChannelId} failed", s.ChannelId);
@@ -135,6 +139,10 @@
                     {
                         await s.Sender.SendPacketAsync(packet);
                     }
+                    catch (OperationCanceledException)
+                    {
+                        // Sending was canceled, likely due to the session being disconnected; ignore.
+                    }
                     catch (Exception ex)
                     {
                         logger.Warning(ex, "Broadcast to {ChannelId} failed", s.ChannelId);
EOF
@@ -117,6 +117,10 @@
{
await s.Sender.SendPacketAsync(packet);
}
catch (OperationCanceledException)
{
// Sending was canceled, likely due to the session being disconnected; ignore.
}
catch (Exception ex)
{
logger.Warning(ex, "Broadcast to {ChannelId} failed", s.ChannelId);
@@ -135,6 +139,10 @@
{
await s.Sender.SendPacketAsync(packet);
}
catch (OperationCanceledException)
{
// Sending was canceled, likely due to the session being disconnected; ignore.
}
catch (Exception ex)
{
logger.Warning(ex, "Broadcast to {ChannelId} failed", s.ChannelId);
Copilot is powered by AI and may make mistakes. Always verify output.
Comment on lines +120 to +123
catch (Exception ex)
{
logger.Warning(ex, "Broadcast to {ChannelId} failed", s.ChannelId);
}

Check notice

Code scanning / CodeQL

Generic catch clause Note

Generic catch clause.

Copilot Autofix

AI about 20 hours ago

General fix: narrow the catch from Exception to the specific, expected exceptions that represent per-session send failures, and allow others (notably OperationCanceledException and TaskCanceledException) to propagate. This keeps the behavior of “log and continue” for normal connection problems but avoids masking cancellation or unrelated runtime errors.

Best targeted change here: keep a single catch (Exception ex) but add an early rethrow for cancellation-related exceptions, which are the ones most likely to indicate an intentional shutdown or cooperative cancellation rather than an I/O failure. This preserves logging and behavior for all other exceptions while avoiding silently eating cancellations. We apply the same pattern to both BroadcastPacketAsync overloads.

Concretely, in src/NosCore.GameObject/Services/BroadcastService/SessionRegistry.cs, inside both async lambdas in:

  • public async Task BroadcastPacketAsync(IPacket packet) (lines 112–125)
  • public async Task BroadcastPacketAsync(IPacket packet, string excludeChannelId) (lines 128–143)

update the catch (Exception ex) body to:

  • Check if (ex is OperationCanceledException) and throw; in that case.
  • Then log the warning as before for all other exceptions.

No new imports are strictly needed because OperationCanceledException is in System, which is already imported.


Suggested changeset 1
src/NosCore.GameObject/Services/BroadcastService/SessionRegistry.cs

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/src/NosCore.GameObject/Services/BroadcastService/SessionRegistry.cs b/src/NosCore.GameObject/Services/BroadcastService/SessionRegistry.cs
--- a/src/NosCore.GameObject/Services/BroadcastService/SessionRegistry.cs
+++ b/src/NosCore.GameObject/Services/BroadcastService/SessionRegistry.cs
@@ -119,6 +119,11 @@
                 }
                 catch (Exception ex)
                 {
+                    if (ex is OperationCanceledException)
+                    {
+                        throw;
+                    }
+
                     logger.Warning(ex, "Broadcast to {ChannelId} failed", s.ChannelId);
                 }
             });
@@ -137,6 +142,11 @@
                     }
                     catch (Exception ex)
                     {
+                        if (ex is OperationCanceledException)
+                        {
+                            throw;
+                        }
+
                         logger.Warning(ex, "Broadcast to {ChannelId} failed", s.ChannelId);
                     }
                 });
EOF
@@ -119,6 +119,11 @@
}
catch (Exception ex)
{
if (ex is OperationCanceledException)
{
throw;
}

logger.Warning(ex, "Broadcast to {ChannelId} failed", s.ChannelId);
}
});
@@ -137,6 +142,11 @@
}
catch (Exception ex)
{
if (ex is OperationCanceledException)
{
throw;
}

logger.Warning(ex, "Broadcast to {ChannelId} failed", s.ChannelId);
}
});
Copilot is powered by AI and may make mistakes. Always verify output.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants