Skip to content

Conversation

@sanjomo
Copy link
Member

@sanjomo sanjomo commented Dec 13, 2025

Description

  1. Introducing Redis stream support , Both Multichannel & single channel support
  2. Redis and Hazelcast - single channel support added
  3. Changed PubSubStore to EventStore as now store can be PubSub/Stream/Broker etc
  4. Distributed Test, Test creates independent servers and communication via adapter will be verified in various scenarios
  5. Redis and Hazelcast added support to choose option like reliable/unreliable

Type of Change

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to not work as expected)
  • Documentation update
  • Code refactoring
  • Test improvements
  • Build/tooling changes

Related Issue

Closes #39

Changes Made

  • Redis stream included - both single channel , multichannel
  • Included single channel support for hazelcast, redis
  • add unreliable and reliable mode while publishing

Testing

  • All existing tests pass
  • New tests added for new functionality
  • Tests pass locally with mvn test
  • Integration tests pass (if applicable)

Checklist

  • Code follows project coding standards
  • Self-review completed
  • Code is commented where necessary
  • Documentation updated (if needed)
  • Commit messages follow conventional format
  • No merge conflicts
  • All CI checks pass

Additional Notes

Any additional information, screenshots, or context that reviewers should know.

Summary by CodeRabbit

  • New Features

    • Added multiple distributed event store implementations supporting single-channel and multi-channel configurations for enhanced scalability and reliability.
    • New dependency management options for distributed deployments.
  • Bug Fixes

    • Improved error logging with parameterized messages for better maintainability.
  • Tests

    • Added comprehensive distributed integration tests across multiple backend implementations.
    • Enhanced test reliability with automatic retry mechanism for flaky tests.

✏️ Tip: You can customize this high-level summary in your review settings.

commit dad09c1
Author: sanjomo <git@santhoshmohan.com>
Date:   Sat Dec 13 21:47:22 2025 +0530

    checkstyle issues

commit c04e9c4
Author: sanjomo <git@santhoshmohan.com>
Date:   Sat Dec 13 20:15:10 2025 +0530

    stream logic change by reliable topic

    Redisson reliable topic -> stream
    Hazalcast reliable topic -> ringbuffer

commit 018d94a
Author: sanjomo <git@santhoshmohan.com>
Date:   Sat Dec 13 13:03:52 2025 +0530

    review comment changes

commit 614ecad
Author: sanjomo <git@santhoshmohan.com>
Date:   Sat Dec 13 12:27:08 2025 +0530

    Update ListenerRegistration.java

commit 313e6c9
Author: sanjomo <git@santhoshmohan.com>
Date:   Sat Dec 13 12:14:14 2025 +0530

    review changes

commit a339591
Author: sanjomo <git@santhoshmohan.com>
Date:   Sat Dec 13 11:47:53 2025 +0530

    review changes

commit b9bfb15
Author: sanjomo <git@santhoshmohan.com>
Date:   Sat Dec 13 01:12:00 2025 +0530

    typo fix

commit eba8962
Author: sanjomo <git@santhoshmohan.com>
Date:   Sat Dec 13 00:33:46 2025 +0530

    Refactor RedisStreamsStore and improve test reliability

    Refactored RedisStreamsStore to improve executor management, use JetBrains @nullable annotations, and enhance shutdown handling. Updated tests to remove unnecessary Thread.sleep calls and ensure proper resource cleanup, improving test reliability and speed. Added org.jetbrains.annotations as a dependency and updated module-info.java accordingly.

commit d0839dc
Author: sanjomo <git@santhoshmohan.com>
Date:   Fri Dec 12 13:48:28 2025 +0530

    review changes and test refactoring

commit c8780e5
Author: sanjomo <git@santhoshmohan.com>
Date:   Fri Dec 12 12:54:59 2025 +0530

    code refactoring

    removed retry mechanism, messages only once processed

commit e74792e
Author: sanjomo <git@santhoshmohan.com>
Date:   Fri Dec 12 02:31:36 2025 +0530

    thread for each channel

commit c82ed3a
Author: sanjomo <git@santhoshmohan.com>
Date:   Fri Dec 12 01:29:29 2025 +0530

    checkstyle changes

commit db96284
Author: sanjomo <git@santhoshmohan.com>
Date:   Fri Dec 12 00:52:39 2025 +0530

    test refactoring

commit fb705a0
Author: sanjomo <git@santhoshmohan.com>
Date:   Fri Dec 12 00:14:26 2025 +0530

    test refactoring

commit 36f53f3
Author: sanjomo <git@santhoshmohan.com>
Date:   Thu Dec 11 23:27:26 2025 +0530

    checkstyle issue fix

commit 269c9cf
Author: sanjomo <git@santhoshmohan.com>
Date:   Thu Dec 11 22:49:41 2025 +0530

    test refactoring

commit 68e9ecc
Author: sanjomo <git@santhoshmohan.com>
Date:   Thu Dec 11 21:57:32 2025 +0530

    license added

commit 8c9d122
Author: sanjomo <git@santhoshmohan.com>
Date:   Thu Dec 11 21:31:45 2025 +0530

    Update RedisStreamsStoreFactory.java

commit 0f75adf
Author: sanjomo <git@santhoshmohan.com>
Date:   Thu Dec 11 21:18:52 2025 +0530

    code refactoring

commit 92f9cea
Author: sanjomo <git@santhoshmohan.com>
Date:   Thu Dec 11 19:11:36 2025 +0530

    code refactoring

commit 3db6cc4
Author: sanjomo <git@santhoshmohan.com>
Date:   Thu Dec 11 16:20:15 2025 +0530

    Update DefaultSocketIOBeans.java

commit ba8e4fd
Author: sanjomo <git@santhoshmohan.com>
Date:   Thu Dec 11 16:12:40 2025 +0530

    Delete DevMain.java

commit c79a9ab
Author: sanjomo <git@santhoshmohan.com>
Date:   Thu Dec 11 16:06:00 2025 +0530

    test cases distributed cases

commit 2683aed
Author: sanjomo <git@santhoshmohan.com>
Date:   Thu Dec 11 13:59:04 2025 +0530

    remove getEnabledTypes as all should be enabled

commit 6cc4694
Merge: 05c3c02 2d5c28f
Author: sanjomo <git@santhoshmohan.com>
Date:   Thu Dec 11 13:58:05 2025 +0530

    Merge branch 'redis-stream-single-channel' of https://github.com/socketio4j/netty-socketio into redis-stream-single-channel

commit 05c3c02
Author: sanjomo <git@santhoshmohan.com>
Date:   Thu Dec 11 13:55:08 2025 +0530

    redis stream multichannel support

commit 2d5c28f
Author: NeatGuyCoding <15627489+NeatGuyCoding@users.noreply.github.com>
Date:   Thu Dec 11 10:45:28 2025 +0800

    refactor(minor): add log for event store exception

    Signed-off-by: NeatGuyCoding <15627489+NeatGuyCoding@users.noreply.github.com>

commit 5ccb56c
Author: NeatGuyCoding <15627489+NeatGuyCoding@users.noreply.github.com>
Date:   Thu Dec 11 09:43:04 2025 +0800

    refactor(minor): add log for event store throwable

    Signed-off-by: NeatGuyCoding <15627489+NeatGuyCoding@users.noreply.github.com>

commit d523d14
Author: sanjomo <git@santhoshmohan.com>
Date:   Tue Dec 9 21:58:57 2025 +0530

    checkstyle change

commit 931ba68
Author: sanjomo <git@santhoshmohan.com>
Date:   Tue Dec 9 21:30:49 2025 +0530

    code refactoring

commit a555ac4
Author: sanjomo <git@santhoshmohan.com>
Date:   Sat Dec 6 20:24:31 2025 +0530

    checkstyle changes

commit 7fd2ffd
Author: sanjomo <git@santhoshmohan.com>
Date:   Sat Dec 6 20:09:48 2025 +0530

    adding different store type

commit 1068397
Author: sanjomo <git@santhoshmohan.com>
Date:   Thu Dec 4 10:44:55 2025 +0530

    checkstyle changes

commit b5ffdab
Author: sanjomo <git@santhoshmohan.com>
Date:   Thu Dec 4 10:28:10 2025 +0530

    test case optimisations

commit 53cb1d3
Author: sanjomo <git@santhoshmohan.com>
Date:   Wed Dec 3 23:10:35 2025 +0530

    boardcast and redissonpub sub test

commit 82bb552
Author: sanjomo <git@santhoshmohan.com>
Date:   Wed Dec 3 20:23:18 2025 +0530

    code review changes

commit 484fd28
Author: sanjomo <git@santhoshmohan.com>
Date:   Wed Dec 3 19:41:32 2025 +0530

    Update DistributedSingleChannelRedisStreamTest.java

commit 983ed11
Author: sanjomo <git@santhoshmohan.com>
Date:   Wed Dec 3 18:37:46 2025 +0530

    test case added, pubsubstore to eventstore name change

commit 6abd6a0
Author: sanjomo <git@santhoshmohan.com>
Date:   Wed Dec 3 16:05:47 2025 +0530

    gitignore autogenerated xml

commit 5fc76c8
Author: sanjomo <git@santhoshmohan.com>
Date:   Wed Dec 3 15:45:39 2025 +0530

    nodeid logic change, constructors stores, other refactoring

commit f3fb2c2
Author: sanjomo <git@santhoshmohan.com>
Date:   Tue Dec 2 23:07:11 2025 +0530

    license add

commit 5fc4af3
Author: sanjomo <git@santhoshmohan.com>
Date:   Tue Dec 2 22:54:16 2025 +0530

    checkstyle changes

commit 2affd53
Author: sanjomo <git@santhoshmohan.com>
Date:   Tue Dec 2 22:33:12 2025 +0530

    review changes

commit ea23a72
Merge: 6aa868a 9945958
Author: sanjomo <git@santhoshmohan.com>
Date:   Tue Dec 2 22:21:13 2025 +0530

    Merge branch 'main' into redis-stream-single-channel

commit 6aa868a
Author: sanjomo <git@santhoshmohan.com>
Date:   Sat Nov 29 15:37:48 2025 +0530

    pubsub to event

commit 768ffd4
Author: sanjomo <git@santhoshmohan.com>
Date:   Fri Nov 28 20:11:00 2025 +0530

    Update SingleChannelRedisStreamsPubSubStore.java

commit 581225f
Author: sanjomo <git@santhoshmohan.com>
Date:   Wed Nov 26 20:45:21 2025 +0530

    redis stream single channel - order guaranteed
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Dec 13, 2025

Walkthrough

This PR replaces the pub/sub store architecture with a new event store abstraction. Changes include relocating store implementations to new packages, introducing EventStore/EventType/EventMessage/EventListener interfaces, removing HazelcastPubSubStore and BaseStoreFactory from the pubsub package, and adding new event-based store implementations (HazelcastEventStore, RedissonEventStore, RedissonReliableEventStore, MemoryEventStore) with corresponding factory classes and integration tests.

Changes

Cohort / File(s) Summary
Build & Module Configuration
pom.xml, .gitignore, netty-socketio-core/pom.xml, netty-socketio-core/src/main/java/module-info.java
Added .gitignore patterns for *.iml and dependency-reduced-pom.xml; added test-scoped jsonassert and annotations dependencies; updated module exports to remove store.pubsub and add store.event, store.hazelcast, store.memory, store.redis_pubsub, store.redis_reliable with new org.jetbrains.annotations requirement.
Core Store Architecture Migration
netty-socketio-core/src/main/java/com/socketio4j/socketio/store/StoreFactory.java, netty-socketio-core/src/main/java/com/socketio4j/socketio/store/event/*
Replaced pubSubStore() with eventStore() in StoreFactory interface. Introduced new event store layer: EventStore interface, EventStoreMode enum (SINGLE_CHANNEL, MULTI_CHANNEL), EventStoreType enum (LOCAL, PUBSUB, STREAM, BROKER), PublishMode enum (RELIABLE, UNRELIABLE), PublishConfig configuration class, EventListener<T> interface, and EventMessage base class with offset support; removed pub/sub variant.
Event Message Types
netty-socketio-core/src/main/java/com/socketio4j/socketio/store/event/(BulkJoinMessage|BulkLeaveMessage|ConnectMessage|DisconnectMessage|DispatchMessage|JoinMessage|LeaveMessage|TestMessage).java
Migrated and restructured message classes from store.pubsub to store.event package; changed base class from PubSubMessage to EventMessage; split combined message types (e.g., BulkJoinLeaveMessageBulkJoinMessage + BulkLeaveMessage, JoinLeaveMessageJoinMessage + LeaveMessage).
Base Factory for Event Stores
netty-socketio-core/src/main/java/com/socketio4j/socketio/store/event/BaseStoreFactory.java
Introduced new abstract BaseStoreFactory implementing StoreFactory with centralized event subscription handling for SINGLE_CHANNEL and MULTI_CHANNEL modes, nodeId management, lifecycle hooks (init, onDisconnect), and offset attachment for dispatch messages; replaces pub/sub-based BaseStoreFactory.
Hazelcast Event Store
netty-socketio-core/src/main/java/com/socketio4j/socketio/store/hazelcast/(HazelcastEventStore|HazelcastStoreFactory).java, netty-socketio-core/src/main/java/com/socketio4j/socketio/store/hazelcast/HazelcastStore.java
Created HazelcastEventStore implementing EventStore with support for SINGLE_CHANNEL and MULTI_CHANNEL modes via Hazelcast topics; added fluent Builder pattern; created HazelcastStoreFactory extending BaseStoreFactory to manage HazelcastStore instances; relocated HazelcastStore to hazelcast package.
Hazelcast Ring Buffer Event Store
netty-socketio-core/src/main/java/com/socketio4j/socketio/store/hazelcast_ringbuffer/HazelcastRingBufferEventStore.java
Added new HazelcastRingBufferEventStore implementing EventStore for durable event streams using Hazelcast reliable topics (RReliableTopic), supporting periodic stream trimming and both SINGLE_CHANNEL and MULTI_CHANNEL modes with fluent Builder.
Memory Event Store
netty-socketio-core/src/main/java/com/socketio4j/socketio/store/memory/(MemoryEventStore|MemoryStore|MemoryStoreFactory).java
Created MemoryEventStore (local, no-op implementation); relocated MemoryStore to memory package; updated MemoryStoreFactory to extend BaseStoreFactory and return MemoryEventStore via eventStore() method.
Redisson Event Stores
netty-socketio-core/src/main/java/com/socketio4j/socketio/store/redis_pubsub/(RedissonEventStore|RedissonStore|RedissonStoreFactory).java
Created RedissonEventStore (pub/sub-based, unreliable); relocated RedissonStore to redis_pubsub package; created RedissonStoreFactory extending BaseStoreFactory to manage RedissonStore instances and provide EventStore integration.
Redisson Reliable Event Store
netty-socketio-core/src/main/java/com/socketio4j/socketio/store/redis_reliable/RedissonReliableEventStore.java
Added new RedissonReliableEventStore implementing EventStore for durable Redis Streams with periodic trimming, configurable retention, SINGLE_CHANNEL and MULTI_CHANNEL support, and fluent Builder; includes RedissonReliableStoreFactory.
Handler & Core Updates
netty-socketio-core/src/main/java/com/socketio4j/socketio/(Configuration|JsonSupportWrapper|SingleRoomBroadcastOperations|SocketIOChannelInitializer|SocketIOServer|annotation/ScannerEngine|handler/(AuthorizeHandler|InPacketHandler)|namespace/Namespace).java
Updated imports and method calls from pubSubStore()/PubSubType/PubSubMessage to eventStore()/EventType/EventMessage; improved logging with parameterized SLF4J messages; updated Namespace to emit granular join/leave events (split message types); updated log labels (e.g., "Session store / pubsub factory" → "Session store / event store factory").
Store Removal
netty-socketio-core/src/main/java/com/socketio4j/socketio/store/HazelcastPubSubStore.java, netty-socketio-core/src/main/java/com/socketio4j/socketio/store/pubsub/BaseStoreFactory.java
Deleted HazelcastPubSubStore (replaced by HazelcastEventStore); deleted BaseStoreFactory from pubsub package (replaced by event package variant).
Integration Tests
netty-socketio-core/src/test/java/com/socketio4j/socketio/integration/Distributed(Hazelcast|Redisson)*.java
Added comprehensive distributed integration tests extending DistributedCommonTest for two-node cluster scenarios using HazelcastEventStore and RedissonEventStore/RedissonReliableEventStore with both SINGLE_CHANNEL and MULTI_CHANNEL modes; covers room broadcasts, cross-node messaging, join/leave, backfill, sender exclusion.
Unit Test Refactoring
netty-socketio-core/src/test/java/com/socketio4j/socketio/(namespace|store)/\*.java
Updated store-related tests to use EventStore instead of PubSubStore; renamed and refactored abstract test base from AbstractPubSubStoreTest to AbstractEventStoreTest with createEventStore() method; updated test assertions and mocking to reference EventStore; updated store factory tests for new constructor signatures.
Framework-Specific Updates
netty-socketio-micronaut/src/main/java/com/socketio4j/socketio/micronaut/config/NettySocketIOConfigurationFactory.java, netty-socketio-quarkus/**, netty-socketio-spring-boot-starter/**
Updated MemoryStoreFactory import paths from root store package to store.memory subpackage; updated Quarkus bean scope to ApplicationScoped; wrapped Arc resource handles with try-with-resources for proper lifecycle management.
Test Infrastructure
netty-socketio-core/src/test/java/com/socketio4j/socketio/store/(AbstractStoreTest|CustomizedHazelcastContainer|HazelcastStoreTest|MemoryStoreTest|RedissonStoreTest).java, pom.xml
Changed AbstractStoreTest container scope from static to instance (per-test lifecycle); added getHazelcastAddress() helper to CustomizedHazelcastContainer; updated HazelcastStoreTest initialization to use fluent ClientConfig; added test retry configuration (rerunFailingTestsCount: 3) to maven-surefire-plugin.

Estimated code review effort

🎯 5 (Critical) | ⏱️ ~120 minutes

Areas requiring extra attention:

  • Architecture-wide migration: The shift from PubSubStore to EventStore affects nearly 40+ files across the codebase; verify consistent migration of all store implementations and their integrations.
  • Event subscription logic: Review BaseStoreFactory.handleSingleChannelSubscribe() and handleMultiChannelSubscribe() for correct message routing, namespace resolution, and offset attachment for dispatch messages.
  • Hazelcast/Redisson implementations: Examine topic naming, message filtering (nodeId checks), listener registration/cleanup, and shutdown order in HazelcastEventStore, HazelcastRingBufferEventStore, RedissonEventStore, and RedissonReliableEventStore.
  • Message type splitting: Verify that splitting combined messages (BulkJoinLeaveMessage → BulkJoinMessage/BulkLeaveMessage) does not break room operation semantics in Namespace and BaseStoreFactory.
  • New enums and configurations: Ensure EventStoreMode, EventStoreType, PublishMode, and PublishConfig are correctly applied across all store implementations and factories.
  • Test coverage: Validate distributed integration tests with diverse store backends (Hazelcast single/multi-channel, Redisson pub/sub/stream single/multi-channel) for correctness and isolation.
  • Module exports: Confirm that module-info.java changes (removal of store.pubsub, addition of new subpackages, new dependency on org.jetbrains.annotations) align with actual package structure and usage.
  • Backward compatibility: Assess if any removed public APIs (e.g., StoreFactory.pubSubStore(), removed onAny listeners in Namespace) impact external users or require deprecation warnings.

Possibly related PRs

Suggested labels

enhancement, refactoring, architecture

Suggested reviewers

  • NeatGuyCoding

Poem

🐰 A store's transformation, from pub to stream,
Event-driven dreams replace the old regime,
Channel modes split, and messages align,
Hazelcast and Redis in harmony combine,
The architecture blooms—a rabbit's delight!

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 20.61% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly describes the main additions: Redis Stream support, single-channel Hazelcast & Redis, and distributed tests—all confirmed by the raw summary and PR description.
Description check ✅ Passed The description covers key points from the template including type of change (Feature/Breaking/Refactoring/Tests), related issue, changes made, and testing status, though documentation update was not completed.
Linked Issues check ✅ Passed The PR successfully implements the #39 feature request for Redis stream adapter by adding EventStore-based stream/pubsub/broker support with single/multi-channel modes and reliable/unreliable publishing options across Redis, Hazelcast, and comprehensive distributed tests.
Out of Scope Changes check ✅ Passed All changes align with the stated objectives: Redis/Hazelcast store implementations, PubSubStore→EventStore refactoring, single-channel support, distributed tests, and dependency/logging updates are all in-scope with no unrelated modifications detected.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch redis-stream-hz-ringbuffer

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@sanjomo sanjomo self-assigned this Dec 13, 2025
UUID sessionId = UUID.randomUUID();
Store store = storeFactory.createStore(sessionId);
PubSubStore pubSubStore = storeFactory.pubSubStore();
EventStore eventStore = storeFactory.eventStore();
@github-actions
Copy link

github-actions bot commented Dec 13, 2025

Qodana for JVM

45 new problems were found

Inspection name Severity Problems
Vulnerable declared dependency 🔶 Warning 8
Pointless arithmetic expression 🔶 Warning 7
Comparison of 'short' and 'char' values 🔶 Warning 2
Result of method call ignored 🔶 Warning 2
Busy wait 🔶 Warning 1
Injection point with ambiguous dependencies 🔶 Warning 1
Constant values 🔶 Warning 1
Number of placeholders does not match number of arguments in logging call 🔶 Warning 1
Unnecessary 'null' check before method call 🔶 Warning 1
Wrapper type may be primitive 🔶 Warning 1
Non-distinguishable logging calls ◽️ Notice 11
Vulnerable declared dependency ◽️ Notice 9

☁️ View the detailed Qodana report

Detected 123 dependencies

Third-party software list

This page lists the third-party software dependencies used in project

Dependency Version Licenses
aesh 2.8.2 Apache-2.0
annotations 26.0.2-1 Apache-2.0
arc-processor 3.30.1 Apache-2.0
arc 3.30.1 Apache-2.0
asm-analysis 9.9 BSD-3-Clause
asm-commons 9.9 BSD-3-Clause
asm-tree 9.9 BSD-3-Clause
asm-util 9.9 BSD-3-Clause
asm 9.9 BSD-3-Clause
byte-buddy 1.17.7 Apache-2.0
cache-api 1.1.1 Apache-2.0
commons-codec 1.20.0 Apache-2.0
commons-compress 1.28.0 Apache-2.0
commons-io 2.21.0 Apache-2.0
commons-logging-jboss-logging 1.0.0.final Apache-2.0
commons-logging 1.3.5 Apache-2.0
crac 1.5.0 BSD-2-Clause
gizmo 1.9.0 Apache-2.0
gizmo2 2.0.0.beta10 Apache-2.0
hazelcast 5.2.5 MIT
jackson-annotations 2.20 Apache-2.0
jackson-core 2.20.1 Apache-2.0
jackson-databind 2.20.1 Apache-2.0
jackson-dataformat-yaml 2.20.0 AML
jackson-datatype-jsr310 2.20.0 Apache-2.0
jakarta.annotation-api 2.1.1 Classpath-exception-2.0
EPL-2.0
GPL-2.0-only
jakarta.annotation-api 3.0.0 Classpath-exception-2.0
EPL-2.0
GPL-2.0-only
jakarta.el-api 6.0.1 Classpath-exception-2.0
EPL-2.0
GPL-2.0-only
jakarta.enterprise.cdi-api 4.1.0 Apache-2.0
jakarta.enterprise.lang-model 4.1.0 Apache-2.0
jakarta.inject-api 2.0.1 Apache-2.0
jakarta.interceptor-api 2.2.0 Classpath-exception-2.0
EPL-2.0
GPL-2.0-only
jakarta.json-api 2.1.3 Classpath-exception-2.0
EPL-2.0
GPL-2.0-only
jakarta.transaction-api 2.0.1 Classpath-exception-2.0
EPL-2.0
GPL-2.0-only
jandex-gizmo2 3.5.2 Apache-2.0
jandex 3.5.2 Apache-2.0
jansi 2.4.0 Apache-2.0
jboss-logging 3.6.1.final Apache-2.0
jboss-logmanager 3.1.2.final Apache-2.0
jboss-threads 3.9.1 Apache-2.0
jctools-core 4.0.5 Apache-2.0
jdk-classfile-backport 25.1 GPL-2.0-only
jodd-util 6.3.0 BSD-2-Clause
jspecify 1.0.0 Apache-2.0
jul-to-slf4j 2.0.17 MIT
kryo 5.6.2 BSD-3-Clause
log4j-api 2.25.2 Apache-2.0
log4j-to-slf4j 2.25.2 Apache-2.0
micrometer-commons 1.16.0 Apache-2.0
micrometer-observation 1.16.0 Apache-2.0
micronaut-aop 4.10.2 Apache-2.0
micronaut-context-propagation 4.10.2 Apache-2.0
micronaut-context 4.10.2 Apache-2.0
micronaut-core-reactive 4.10.2 Apache-2.0
micronaut-core 4.10.2 Apache-2.0
micronaut-discovery-core 4.10.2 Apache-2.0
micronaut-http-server 4.10.2 Apache-2.0
micronaut-http 4.10.2 Apache-2.0
micronaut-inject 4.10.2 Apache-2.0
micronaut-retry 4.10.2 Apache-2.0
micronaut-router 4.10.2 Apache-2.0
micronaut-runtime 4.10.2 Apache-2.0
microprofile-config-api 3.1 Apache-2.0
microprofile-context-propagation-api 1.3 Apache-2.0
minlog 1.3.1 BSD-3-Clause
mutiny 3.0.1 Apache-2.0
nativeimage 23.1.2 UPL-1.0
netty-common 4.2.7.final Apache-2.0
parsson 1.1.7 Classpath-exception-2.0
EPL-2.0
GPL-2.0-only
quarkus-arc-deployment 3.30.1 Apache-2.0
quarkus-arc-dev 3.30.1 Apache-2.0
quarkus-arc 3.30.1 Apache-2.0
quarkus-bootstrap-app-model 3.30.1 Apache-2.0
quarkus-bootstrap-core 3.30.1 Apache-2.0
quarkus-bootstrap-runner 3.30.1 Apache-2.0
quarkus-builder 3.30.1 Apache-2.0
quarkus-class-change-agent 3.30.1 Apache-2.0
quarkus-classloader-commons 3.30.1 Apache-2.0
quarkus-core-deployment 3.30.1 Apache-2.0
quarkus-core 3.30.1 Apache-2.0
quarkus-development-mode-spi 3.30.1 Apache-2.0
quarkus-devui-deployment-spi 3.30.1 Apache-2.0
quarkus-fs-util 1.2.0 Apache-2.0
quarkus-hibernate-validator-spi 3.30.1 Apache-2.0
quarkus-ide-launcher 3.30.1 Apache-2.0
quarkus-smallrye-context-propagation-spi 3.30.1 Apache-2.0
reactive-streams 1.0.4 MIT-0
reactor-core 3.6.2 Apache-2.0
reactor-core 3.7.9 Apache-2.0
readline 2.6 Apache-2.0
redisson 3.52.0 Apache-2.0
reflectasm 1.11.9 BSD-3-Clause
rxjava 3.1.8 Apache-2.0
slf4j-api 2.0.17 MIT
slf4j-jboss-logmanager 2.0.2.final Apache-2.0
smallrye-common-annotation 2.14.0 Apache-2.0
smallrye-common-classloader 2.14.0 Apache-2.0
smallrye-common-constraint 2.14.0 Apache-2.0
smallrye-common-cpu 2.14.0 Apache-2.0
smallrye-common-expression 2.14.0 Apache-2.0
smallrye-common-function 2.14.0 Apache-2.0
smallrye-common-io 2.14.0 Apache-2.0
smallrye-common-net 2.14.0 Apache-2.0
smallrye-common-os 2.14.0 Apache-2.0
smallrye-common-process 2.14.0 Apache-2.0
smallrye-common-ref 2.14.0 Apache-2.0
smallrye-common-resource 2.14.0 Apache-2.0
smallrye-config-common 3.14.1 Apache-2.0
smallrye-config-core 3.14.1 Apache-2.0
smallrye-config 3.14.1 Apache-2.0
snakeyaml 2.4 Apache-2.0
snakeyaml 2.5 Apache-2.0
spring-aop 7.0.1 Apache-2.0
spring-beans 7.0.1 Apache-2.0
spring-boot-autoconfigure 4.0.0 Apache-2.0
spring-boot-starter-logging 4.0.0 Apache-2.0
spring-boot-starter 4.0.0 Apache-2.0
spring-boot 4.0.0 Apache-2.0
spring-context 7.0.1 Apache-2.0
spring-core 7.0.1 Apache-2.0
spring-expression 7.0.1 Apache-2.0
wildfly-common 2.0.1 Apache-2.0
word 23.1.2 UPL-1.0
Contact Qodana team

Contact us at qodana-support@jetbrains.com

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 13

Note

Due to the large number of review comments, Critical, Major severity comments were prioritized as inline comments.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (5)
pom.xml (1)

562-580: Fix incorrect module name in --add-opens and consider gating test retries via CI profile.

The --add-opens netty.socketio/com.socketio4j.socketio.store.pubsub (lines 569, 571) references a non-existent package; the actual package is com.socketio4j.socketio.store.redis_pubsub (which is already exported in module-info). Either update these flags to use the correct package name or remove them if no longer needed.

Also, rerunFailingTestsCount=3 is unconditional and applies to all test runs. Test retries can mask flakiness. Consider gating this via a CI profile and reporting when retries occur, so real nondeterminism surfaces rather than silently passing.

netty-socketio-core/src/test/java/com/socketio4j/socketio/store/AbstractStoreTest.java (1)

43-64: Container lifecycle changed from per-class to per-test without explicit cleanup.

The container field was changed from static to instance, removing the @AfterAll cleanup. While the container starts in @BeforeEach, there's no explicit stop in @AfterEach. This could lead to:

  1. Resource leaks if containers accumulate without cleanup
  2. Slower test execution due to repeated container creation/destruction

Consider either:

  • Restoring the static container for shared lifecycle across tests, or
  • Adding explicit container cleanup in @AfterEach:
 @AfterEach
 public void tearDown() throws Exception {
     if (store != null) {
         // Clean up store data
         cleanupStore();
     }
+    if (container != null && container.isRunning()) {
+        container.stop();
+    }
 }
netty-socketio-core/src/main/java/com/socketio4j/socketio/store/memory/MemoryStoreFactory.java (1)

27-43: Call memoryEventStore.shutdown() in the factory's shutdown method.
All other StoreFactory implementations (Hazelcast, Redis PubSub, Redis Stream) cascade the shutdown call to their eventStore. For consistency and to prepare for future resource management in MemoryEventStore, add the same pattern here.

     @Override
     public void shutdown() {
+        memoryEventStore.shutdown();
     }

(The getEventStoreMode() check in BaseStoreFactory.init(...) is safe; MemoryEventStore returns EventStoreMode.SINGLE_CHANNEL.)

netty-socketio-core/src/main/java/com/socketio4j/socketio/store/redis_pubsub/RedissonStoreFactory.java (1)

71-82: Same issue: publishConfig parameter is unused.

This constructor also accepts but ignores publishConfig. Consider refactoring both constructors to either use the config or remove the parameter.

netty-socketio-core/src/main/java/com/socketio4j/socketio/store/hazelcast/HazelcastStoreFactory.java (1)

63-73: publishConfig parameter is accepted but never used.

Similar to RedissonStoreFactory, this constructor accepts PublishConfig publishConfig but never passes it to HazelcastEventStore. This inconsistency could mislead API consumers who expect their publish configuration to be applied.

♻️ Duplicate comments (3)
netty-socketio-quarkus/netty-socketio-quarkus-runtime/src/main/java/com/socketio4j/socketio/quarkus/recorder/NettySocketIOConfigRecorder.java (3)

146-151: Same bean lifecycle issue as ExceptionListener.

This block has the same critical issue with try-with-resources destroying dependent-scoped beans while retaining references. Apply the same fix as suggested for the ExceptionListener block.


153-158: Same bean lifecycle issue as ExceptionListener.

This block has the same critical issue with try-with-resources destroying dependent-scoped beans while retaining references. Apply the same fix as suggested for the ExceptionListener block.


160-165: Same bean lifecycle issue as ExceptionListener.

This block has the same critical issue with try-with-resources destroying dependent-scoped beans while retaining references. Apply the same fix as suggested for the ExceptionListener block.

🟡 Minor comments (12)
netty-socketio-core/src/main/java/module-info.java-13-19 (1)

13-19: Change org.jetbrains.annotations to requires static.

The JetBrains annotations package should use requires static org.jetbrains.annotations rather than requires, since these annotations (@NotNull, @Nullable, etc.) are compile-time only and should not be a runtime dependency.

Additionally, lines 16-19 and line 37 use 4-space indentation instead of the 2-space indentation used elsewhere in the file; consider aligning the formatting.

netty-socketio-core/src/test/java/com/socketio4j/socketio/integration/DistributedRedissonPubSubSingleChannelUnreliableTest.java-39-49 (1)

39-49: Avoid findAvailablePort() TOCTOU race by binding the probe socket to loopback.

new ServerSocket(0) then releasing the port creates a race condition with other processes/tests acquiring the port. Since test servers connect to localhost, bind the probe socket to loopback to reduce the attack surface:

-import java.net.ServerSocket;
+import java.net.InetAddress;
+import java.net.ServerSocket;
@@
     private int findAvailablePort() throws Exception {
-        try (ServerSocket socket = new ServerSocket(0)) {
+        try (ServerSocket socket = new ServerSocket(0, 0, InetAddress.getLoopbackAddress())) {
             return socket.getLocalPort();
         }
     }

Note: Configuration.setPort(0) exists but SocketIOServer does not expose the bound port post-start via public API, so the loopback probe approach is the practical solution.

netty-socketio-core/src/test/java/com/socketio4j/socketio/integration/DistributedHazelcastRingBufferSingleChannelTest.java-55-57 (1)

55-57: Fix misleading comment - this is Hazelcast, not Redis.

The comment says "Redis + Node Setup" but this test uses Hazelcast. This appears to be a copy-paste error from a Redis-based test.

     // -------------------------------------------
-    // Redis + Node Setup
+    // Hazelcast + Node Setup
     // -------------------------------------------
netty-socketio-core/src/test/java/com/socketio4j/socketio/store/StoreFactoryTest.java-85-91 (1)

85-91: Fix stale assertion message referencing PubSubStore.

The assertion message on line 90 still references "PubSubStore interface" but should say "EventStore interface" to match the updated test.

     @Test
     public void testCreateEventStore() {
         EventStore eventStore = storeFactory.eventStore();
         
         assertNotNull(eventStore, "EventStore should not be null");
-        assertInstanceOf(EventStore.class, eventStore, "EventStore should implement PubSubStore interface");
+        assertInstanceOf(EventStore.class, eventStore, "EventStore should implement EventStore interface");
     }
netty-socketio-core/src/test/java/com/socketio4j/socketio/store/MemoryStoreFactoryTest.java-63-69 (1)

63-69: Fix stale assertion messages referencing PubSubStore.

The assertion messages on lines 67-68 still reference the old PubSubStore/MemoryPubSubStore names but should reference EventStore/MemoryEventStore.

     @Test
     public void testMemoryEventStore() {
         EventStore eventStore = storeFactory.eventStore();
         
-        assertNotNull(eventStore, "PubSubStore should not be null");
-        assertTrue(eventStore instanceof MemoryEventStore, "PubSubStore should be MemoryPubSubStore");
+        assertNotNull(eventStore, "EventStore should not be null");
+        assertTrue(eventStore instanceof MemoryEventStore, "EventStore should be MemoryEventStore");
     }
netty-socketio-core/src/test/java/com/socketio4j/socketio/store/event/RedissonStreamEventStoreTest.java-28-30 (1)

28-30: Class name doesn't match the implementation being tested.

The class is named RedissonStreamEventStoreTest, suggesting it tests Redis Streams functionality, but it instantiates RedissonEventStore from the redis_pubsub package. Based on the relevant code snippet, RedissonEventStore uses RTopic (Redis Pub/Sub), not RStream (Redis Streams).

If this test is intended for Redis Pub/Sub, rename it to RedissonPubSubEventStoreTest. If it should test Redis Streams, it should use the appropriate Stream-based EventStore implementation (e.g., from redis_stream package).

-public class RedissonStreamEventStoreTest extends AbstractEventStoreTest {
+public class RedissonPubSubEventStoreTest extends AbstractEventStoreTest {

Also applies to: 41-49

netty-socketio-core/src/test/java/com/socketio4j/socketio/store/RedissonStreamStoreFactoryTest.java-104-110 (1)

104-110: Update stale assertion messages from PubSubStore to EventStore.

The assertion messages still reference the old "PubSubStore" naming convention but the test validates EventStore and RedissonEventStore. This is inconsistent with the migration to EventStore.

     @Test
     public void testRedissonEventStore() {
         EventStore eventStore = storeFactory.eventStore();
         
-        assertNotNull(eventStore, "PubSubStore should not be null");
-        assertTrue(eventStore instanceof RedissonEventStore, "PubSubStore should be RedissonPubSubStore");
+        assertNotNull(eventStore, "EventStore should not be null");
+        assertTrue(eventStore instanceof RedissonEventStore, "EventStore should be RedissonEventStore");
     }
netty-socketio-core/src/test/java/com/socketio4j/socketio/store/HazelcastRingBufferStoreFactoryTest.java-110-114 (1)

110-114: Fix misleading assertion message.

The assertion message states "EventStore should be HazelcastStore" but the actual check verifies HazelcastEventStore.class.

     public void testHazelcastEventStore() {
         EventStore eventStore = storeFactory.eventStore();
         
         assertNotNull(eventStore, "EventStore should not be null");
-        assertInstanceOf(HazelcastEventStore.class, eventStore, "EventStore should be HazelcastStore");
+        assertInstanceOf(HazelcastEventStore.class, eventStore, "EventStore should be HazelcastEventStore");
     }
netty-socketio-core/src/main/java/com/socketio4j/socketio/store/redis_stream/RedissonStreamStoreFactory.java-73-84 (1)

73-84: Remove unused publishConfig parameter.

The publishConfig parameter is accepted but never used in this constructor. It's not passed to RedissonStreamEventStore or stored anywhere. This pattern is consistent across all store factory implementations (Redisson, RedissonStream, and Hazelcast), suggesting the parameter should either be removed from the signature or the event store implementations should be extended to support it.

netty-socketio-core/src/main/java/com/socketio4j/socketio/store/hazelcast_ringbuffer/HazelcastRingBufferEventStore.java-134-139 (1)

134-139: shutdown0 calls unsubscribe instead of unsubscribe0, causing redundant error logging.

The unsubscribe method in EventStore wraps unsubscribe0 with try-catch logging. Calling this::unsubscribe from shutdown0 will result in errors being logged twice (once here if re-thrown, once in the wrapper).

     @Override
     public void shutdown0() {
-        Arrays.stream(EventType.values()).forEach(this::unsubscribe);
+        Arrays.stream(EventType.values()).forEach(this::unsubscribe0);
         map.clear();
         //do not shut down client here
     }
netty-socketio-core/src/main/java/com/socketio4j/socketio/store/redis_stream/RedissonStreamEventStore.java-255-261 (1)

255-261: Redundant shutdown() followed by shutdownNow().

Calling trimExecutor.shutdown() at line 257 and then trimExecutor.shutdownNow() at line 260 is redundant. The shutdownNow() call immediately interrupts tasks, negating the orderly shutdown initiated by shutdown().

Either use shutdown() with awaitTermination() for graceful shutdown, or use shutdownNow() alone for immediate termination:

 @Override
 public void shutdown0() {
-    trimExecutor.shutdown();
     Arrays.stream(EventType.values()).forEach(this::unsubscribe);
     map.clear();
     trimExecutor.shutdownNow();
 }
netty-socketio-core/src/test/java/com/socketio4j/socketio/integration/DistributedCommonTest.java-506-516 (1)

506-516: Document the ID matching assumption from Socket.IO protocol.

The comparison assumes a.id() (from the Socket.IO client) matches the server-side getSessionId(). This is guaranteed by the Socket.IO protocol—the server sends its session ID to the client in the CONNECT packet, which the client stores as id(). However, clarify this assumption in a comment to make the code's intent explicit.

🧹 Nitpick comments (34)
netty-socketio-core/src/main/java/com/socketio4j/socketio/JsonSupportWrapper.java (1)

47-47: Consider defensive wrapping of src.readLine() in catch blocks.

The src.readLine() calls in the logging statements could potentially throw IOException, which would mask the original exception. While this is a pre-existing pattern, wrapping these calls in a try-catch would ensure the original diagnostic context is always logged.

Example for line 47:

 } catch (Exception e) {
     src.reset();
-    log.error("Can't read ack args: {} for type: {}", src.readLine(), callback.getResultClass(), e);
+    String line = "unknown";
+    try {
+        line = src.readLine();
+    } catch (IOException readErr) {
+        // Ignore read failure during error logging
+    }
+    log.error("Can't read ack args: {} for type: {}", line, callback.getResultClass(), e);
     throw new IOException(e);
 }

Also applies to: 58-58

netty-socketio-core/src/main/java/module-info.java (2)

20-20: Fix indentation for consistency.

Line 20 has 4-space indentation instead of 2, inconsistent with the rest of the file.


37-37: Consider using static requires for compile-time annotations.

The org.jetbrains.annotations module provides compile-time-only annotations (CLASS retention) like @NotNull and @Nullable that are not needed at runtime. Declare this as static requires org.jetbrains.annotations; to align with JPMS semantics and ensure it is treated as an optional dependency. Verify the corresponding build configuration uses compileOnly (Gradle) or provided scope (Maven).

Also note the indentation inconsistency (4 spaces instead of 2).

netty-socketio-core/src/main/java/com/socketio4j/socketio/store/redis_pubsub/RedissonStore.java (1)

17-27: Package move looks consistent; consider containing the unchecked cast warning in get().
If Store#get is intentionally generic-over-Object, add a local @SuppressWarnings("unchecked") to avoid propagating compiler noise and make the tradeoff explicit.

     @Override
+    @SuppressWarnings("unchecked")
     public <T> T get(String key) {
         return (T) map.get(key);
     }

Also applies to: 40-43

netty-socketio-core/src/main/java/com/socketio4j/socketio/store/event/EventStoreMode.java (1)

17-22: EventStoreMode enum is clean and self-explanatory.
Optional: add a short Javadoc on when SINGLE vs MULTI is expected to be used.

netty-socketio-core/src/main/java/com/socketio4j/socketio/store/StoreFactory.java (1)

26-36: Consider a deprecated compatibility shim for pubSubStore() to ease downstream migration.
Since this is a breaking API, a default method can reduce immediate churn for integrators while still steering them to eventStore().

 public interface StoreFactory extends Disconnectable {

     EventStore eventStore();
+
+    /**
+     * @deprecated Use {@link #eventStore()}.
+     */
+    @Deprecated
+    default EventStore pubSubStore() {
+        return eventStore();
+    }

     <K, V> Map<K, V> createMap(String name);
netty-socketio-core/src/main/java/com/socketio4j/socketio/store/event/BulkLeaveMessage.java (1)

22-50: Make BulkLeaveMessage safer for cross-thread / serialization use (immutability + defensive copies + null checks).
Right now callers can mutate rooms after construction (or via the getter), and nulls can slip through until later.

 package com.socketio4j.socketio.store.event;

+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Objects;
 import java.util.Set;
 import java.util.UUID;

 public class BulkLeaveMessage extends EventMessage {

     private static final long serialVersionUID = 4084111480148299012L;

-    private UUID sessionId;
-    private String namespace;
-    private Set<String> rooms;
+    private UUID sessionId;
+    private String namespace;
+    private Set<String> rooms;

     public BulkLeaveMessage() {
     }

-    public BulkLeaveMessage(UUID id, Set<String> rooms, String namespace) {
+    public BulkLeaveMessage(UUID sessionId, Set<String> rooms, String namespace) {
         super();
-        this.sessionId = id;
-        this.rooms = rooms;
-        this.namespace = namespace;
+        this.sessionId = Objects.requireNonNull(sessionId, "sessionId must not be null");
+        this.namespace = Objects.requireNonNull(namespace, "namespace must not be null");
+        Objects.requireNonNull(rooms, "rooms must not be null");
+        this.rooms = Collections.unmodifiableSet(new HashSet<>(rooms));
     }
@@
     public Set<String> getRooms() {
         return rooms;
     }
 }
netty-socketio-core/src/test/java/com/socketio4j/socketio/integration/DistributedRedissonPubSubSingleChannelUnreliableTest.java (1)

39-39: withReuse(true) + explicit stop() is potentially contradictory—decide one behavior.
If you want deterministic cleanup in CI, drop reuse. If you want reuse locally, consider not stopping (or gate reuse with an env/property).

Also applies to: 111-129

netty-socketio-core/src/main/java/com/socketio4j/socketio/store/event/ListenerRegistration.java (1)

25-42: Encapsulate fields (private final) since this is a public API type.
Even though getters exist, package-private fields allow same-package mutation/reflection patterns you probably don’t want long-term.

 public final class ListenerRegistration<T extends EventMessage> {
-    final EventListener<T> listener;
-    final Class<T> clazz;
+    private final EventListener<T> listener;
+    private final Class<T> clazz;
netty-socketio-core/src/test/java/com/socketio4j/socketio/integration/DistributedRedissonStreamMultiChannelTest.java (1)

53-102: Consider removing or replacing the commented-out sleep.

The setup logic is correct and follows the established pattern for distributed tests. However, the commented-out Thread.sleep(600) at line 101 suggests there may have been timing concerns during development.

If synchronization is needed for nodes to be ready, consider using a proper readiness check (e.g., Awaitility) rather than a fixed sleep. If the sleep is no longer needed, remove the commented line to keep the code clean.

         port2 = cfg2.getPort();
-
-        //Thread.sleep(600);
     }
netty-socketio-core/src/test/java/com/socketio4j/socketio/integration/DistributedRedissonStreamSingleChannelTest.java (1)

36-108: Consider reducing duplication with multi-channel test.

This test is nearly identical to DistributedRedissonStreamMultiChannelTest, differing only in EventStoreMode.SINGLE_CHANNEL vs MULTI_CHANNEL. This pattern is repeated across multiple test classes in this PR.

Consider using JUnit 5's @ParameterizedTest with @EnumSource to test both channel modes in a single class, or extract common setup logic to a shared base class or utility:

// Example: Parameterized approach
@ParameterizedTest
@EnumSource(EventStoreMode.class)
void testDistributedRedissonStream(EventStoreMode mode) throws Exception {
    // Setup with the given mode
    cfg1.setStoreFactory(new RedissonStreamStoreFactory(redisClient1, mode));
    // ... rest of test
}

This would significantly reduce maintenance burden as the number of store types and modes grows.

netty-socketio-core/src/test/java/com/socketio4j/socketio/integration/DistributedHazelcastPubSubMultiChannelUnReliableTest.java (2)

24-25: Remove unused imports.

Redisson, Config, and RedissonStoreFactory are imported but never used in this Hazelcast-only test class.

-import org.redisson.Redisson;
-import org.redisson.config.Config;
 
 import com.hazelcast.client.HazelcastClient;
 import com.hazelcast.client.config.ClientConfig;
@@ -33,7 +31,6 @@ import com.socketio4j.socketio.store.CustomizedHazelcastContainer;
 import com.socketio4j.socketio.store.event.EventStoreMode;
 import com.socketio4j.socketio.store.event.PublishConfig;
 import com.socketio4j.socketio.store.hazelcast.HazelcastStoreFactory;
-import com.socketio4j.socketio.store.redis_pubsub.RedissonStoreFactory;

Also applies to: 36-36


54-56: Fix misleading comment.

The comment says "Redis + Node Setup" but this test sets up Hazelcast nodes.

     // -------------------------------------------
-    // Redis + Node Setup
+    // Hazelcast + Node Setup
     // -------------------------------------------
netty-socketio-core/src/test/java/com/socketio4j/socketio/integration/DistributedHazelcastRingBufferMultiChannelTest.java (2)

24-26: Remove unused imports.

Redisson, RedissonClient, Config, CustomizedRedisContainer, and RedissonStreamStoreFactory are imported but never used in this Hazelcast-only test class.

-import org.redisson.Redisson;
-import org.redisson.api.RedissonClient;
-import org.redisson.config.Config;
 
 import com.hazelcast.client.HazelcastClient;
 import com.hazelcast.client.config.ClientConfig;
@@ -31,9 +28,7 @@ import com.socketio4j.socketio.Configuration;
 import com.socketio4j.socketio.SocketIOServer;
 import com.socketio4j.socketio.store.CustomizedHazelcastContainer;
-import com.socketio4j.socketio.store.CustomizedRedisContainer;
 import com.socketio4j.socketio.store.event.EventStoreMode;
 import com.socketio4j.socketio.store.hazelcast_ringbuffer.HazelcastRingBufferStoreFactory;
-import com.socketio4j.socketio.store.redis_stream.RedissonStreamStoreFactory;

Also applies to: 34-34, 37-37


55-57: Fix misleading comment.

The comment says "Redis + Node Setup" but this test sets up Hazelcast RingBuffer nodes.

     // -------------------------------------------
-    // Redis + Node Setup
+    // Hazelcast RingBuffer + Node Setup
     // -------------------------------------------
netty-socketio-core/src/main/java/com/socketio4j/socketio/store/event/PublishConfig.java (1)

52-66: Consider adding factory helper with explicit default mode.

The factory helpers are convenient. For completeness, you might consider adding a static factory that accepts the default mode directly for custom configurations.

public static PublishConfig withDefault(PublishMode defaultMode) {
    return new PublishConfig(defaultMode, Collections.emptyMap());
}

public static PublishConfig withDefault(PublishMode defaultMode, Map<EventType, PublishMode> overrides) {
    return new PublishConfig(defaultMode, overrides);
}
netty-socketio-core/src/test/java/com/socketio4j/socketio/integration/DistributedHazelcastPubSubSingleChannelUnreliableTest.java (2)

24-36: Remove unused Redis-related imports.

These imports reference Redis/Redisson classes that are not used in this Hazelcast-focused test. They appear to be copy-paste artifacts.

 import org.junit.jupiter.api.AfterAll;
 import org.junit.jupiter.api.BeforeAll;
 import org.junit.jupiter.api.TestInstance;
-import org.redisson.config.Config;
 
 import com.hazelcast.client.HazelcastClient;
 import com.hazelcast.client.config.ClientConfig;
 import com.hazelcast.core.HazelcastInstance;
 import com.socketio4j.socketio.Configuration;
 import com.socketio4j.socketio.SocketIOServer;
 import com.socketio4j.socketio.store.CustomizedHazelcastContainer;
-import com.socketio4j.socketio.store.CustomizedRedisContainer;
 import com.socketio4j.socketio.store.event.EventStoreMode;
 import com.socketio4j.socketio.store.event.PublishConfig;
 import com.socketio4j.socketio.store.hazelcast.HazelcastStoreFactory;
-import com.socketio4j.socketio.store.redis_pubsub.RedissonStoreFactory;

114-118: Remove unused redisConfig helper method.

This method is not used anywhere in the Hazelcast test and appears to be a leftover from copy-paste.

-    private Config redisConfig(String url) {
-        Config c = new Config();
-        c.useSingleServer().setAddress(url);
-        return c;
-    }
netty-socketio-core/src/main/java/com/socketio4j/socketio/store/hazelcast/HazelcastEventStore.java (1)

49-62: Consider consistent null handling for eventStoreMode.

The constructor validates hazelcastPub, hazelcastSub, and nodeId with requireNonNull, but allows eventStoreMode to be null and defaults it to MULTI_CHANNEL. This inconsistency could mask configuration errors. Consider either:

  1. Requiring non-null and making the default explicit at the call site, or
  2. Documenting the default behavior clearly
     public HazelcastEventStore(HazelcastInstance hazelcastPub, HazelcastInstance hazelcastSub, Long nodeId, EventStoreMode eventStoreMode) {
 
         Objects.requireNonNull(hazelcastPub, "hazelcastPub cannot be null");
         Objects.requireNonNull(hazelcastSub, "hazelcastSub cannot be null");
         Objects.requireNonNull(nodeId, "nodeId cannot be null");
 
-        if (eventStoreMode == null){
-            eventStoreMode = EventStoreMode.MULTI_CHANNEL;
-        }
+        // Default to MULTI_CHANNEL if not specified
+        this.eventStoreMode = (eventStoreMode != null) ? eventStoreMode : EventStoreMode.MULTI_CHANNEL;
         this.hazelcastPub = hazelcastPub;
         this.hazelcastSub = hazelcastSub;
         this.nodeId = nodeId;
-        this.eventStoreMode = eventStoreMode;
     }
netty-socketio-core/src/main/java/com/socketio4j/socketio/store/hazelcast_ringbuffer/HazelcastRingBufferStoreFactory.java (2)

41-41: Remove unused logger field.

The log field is declared but never used in this class.

 public class HazelcastRingBufferStoreFactory extends BaseStoreFactory {
 
-    private static final Logger log = LoggerFactory.getLogger(HazelcastRingBufferStoreFactory.class);
-
     private final HazelcastInstance hazelcastClient;

53-61: Add a comment or named constant to clarify the intentional null parameter.

The constructor passes null as the ringBufferNamePrefix parameter to HazelcastRingBufferEventStore, which is intentionally handled by the EventStore constructor to use the DEFAULT_RING_BUFFER_NAME_PREFIX. While this is a valid pattern for optional configuration with defaults, adding a comment here (e.g., /* use default prefix */) or extracting a named constant would improve readability for future maintainers.

netty-socketio-core/src/main/java/com/socketio4j/socketio/store/redis_stream/RedissonStreamStoreFactory.java (1)

39-39: Remove unused logger field.

The log field is declared but never used in this class.

 public class RedissonStreamStoreFactory extends BaseStoreFactory {
 
-    private static final Logger log = LoggerFactory.getLogger(RedissonStreamStoreFactory.class);
-
     private final RedissonClient redisClient;
netty-socketio-core/src/test/java/com/socketio4j/socketio/store/event/AbstractEventStoreTest.java (2)

34-36: Update outdated Javadoc comment.

The comment still references "PubSub store tests" but the class now tests EventStore.

 /**
- * Abstract base class for PubSub store tests
+ * Abstract base class for EventStore tests
  */

78-134: testBasicPublishSubscribe and testMessageFiltering are functionally identical.

Both tests subscribe to EventType.DISPATCH, publish a message from a different node, and verify receipt. The only difference is the test description comment. Either consolidate them or differentiate their test logic (e.g., one could test same-node filtering by using the same nodeId).

netty-socketio-core/src/main/java/com/socketio4j/socketio/store/event/LeaveMessage.java (1)

25-37: Consider adding setters for serialization compatibility.

Many serialization frameworks (e.g., Jackson for JSON, Kryo) require setters or field-level access to deserialize objects. The no-arg constructor is present, but without setters, deserialization may fail depending on the framework configuration.

If immutability is intended, mark fields final and ensure the serializer is configured appropriately.

+    public void setSessionId(UUID sessionId) {
+        this.sessionId = sessionId;
+    }
+
+    public void setNamespace(String namespace) {
+        this.namespace = namespace;
+    }
+
+    public void setRoom(String room) {
+        this.room = room;
+    }
netty-socketio-core/src/main/java/com/socketio4j/socketio/store/hazelcast_ringbuffer/HazelcastRingBufferEventStore.java (1)

51-52: Unused constant DEFAULT_RING_BUFFER_MAX_LENGTH.

This constant is defined but never used in the implementation. Either remove it or apply it to configure the ring buffer capacity.

netty-socketio-core/src/test/java/com/socketio4j/socketio/integration/DistributedRedissonPubSubMultiChannelUnReliableTest.java (3)

102-102: Remove commented-out debug code.

The commented Thread.sleep(600) appears to be leftover debug code.

         port2 = cfg2.getPort();
-
-        //Thread.sleep(600);
     }

71-78: Consider extracting duplicate event listener registration.

The event listeners for join-room and leave-room are identical for both nodes. Consider extracting a helper method to reduce duplication.

private void registerEventListeners(SocketIOServer node) {
    node.addEventListener("join-room", String.class, (c, room, ack) -> {
        c.joinRoom(room);
        c.sendEvent("join-ok", "OK");
    });
    node.addEventListener("leave-room", String.class, (c, room, ack) -> {
        c.leaveRoom(room);
        c.sendEvent("leave-ok", "OK");
    });
}

Also applies to: 91-98


126-128: Redundant null check on static final field.

REDIS_CONTAINER is declared as static final and initialized inline, so it can never be null.

-        if (REDIS_CONTAINER != null) {
-            REDIS_CONTAINER.stop();
-        }
+        REDIS_CONTAINER.stop();
netty-socketio-core/src/main/java/com/socketio4j/socketio/store/event/BaseStoreFactory.java (1)

120-132: Consider defensive copy before mutation.

attachOffset mutates the packet's data list. While a new ArrayList is created from existing args, the packet itself is being modified. If the same packet instance is reused elsewhere, this could cause unintended side effects.

netty-socketio-core/src/main/java/com/socketio4j/socketio/store/redis_stream/RedissonStreamEventStore.java (1)

240-243: Null check on topic is unnecessary.

redissonSub.getReliableTopic(...) never returns null (it creates the topic reference if it doesn't exist). The null check at lines 241-243 is dead code.

 RReliableTopic topic = redissonSub.getReliableTopic(getStreamName(type));
-if (topic == null) {
-    return;
-}
 for (Object id : regIds) {
netty-socketio-core/src/main/java/com/socketio4j/socketio/store/redis_pubsub/RedissonEventStore.java (3)

51-63: Avoid reassigning ctor param; normalize mode in one expression. Also consider requireNonNull(eventStoreMode) if you don’t want implicit defaults.

-        if (eventStoreMode == null){
-            eventStoreMode = EventStoreMode.MULTI_CHANNEL;
-        }
-        this.eventStoreMode = eventStoreMode;
+        this.eventStoreMode = (eventStoreMode != null) ? eventStoreMode : EventStoreMode.MULTI_CHANNEL;

91-111: Redisson API + redundant null check + null-safety. getTopic(...) is typically non-null; if that’s true for your Redisson version, if (topic == null) is dead code. Also add Objects.requireNonNull(type) to avoid NPE.

     public void unsubscribe0(EventType type) {
+        Objects.requireNonNull(type, "type is null");

         Queue<Integer> regIds = map.remove(type);
         if (regIds == null || regIds.isEmpty()) {
             return;
         }

         RTopic topic = redissonSub.getTopic(getStreamName(type));
-        if (topic == null) {
-            return;
-        }

         for (Integer id : regIds) {
             try {
                 topic.removeListener(id);
             } catch (Exception ex) {
                 log.warn("Failed to remove listener {} from topic {}", id, getStreamName(type), ex);
             }
         }
     }

113-117: Shutdown semantics: consider blocking new subscribe/publish during shutdown. map.clear() after mass-unsubscribe is fine, but without a “closed” flag, concurrent subscribe calls during shutdown can re-add listeners and leak.

Comment on lines +161 to +171
<dependency>
<groupId>org.skyscreamer</groupId>
<artifactId>jsonassert</artifactId>
<version>1.5.3</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.jetbrains</groupId>
<artifactId>annotations</artifactId>
<version>26.0.2-1</version>
</dependency>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

# Find all Java source files and search for jetbrains annotations usage
rg "import org\.jetbrains\." --type java -n

Repository: socketio4j/netty-socketio

Length of output: 509


🏁 Script executed:

# Also search for specific annotation usage patterns
rg "@(Nullable|NotNull|Contract|Immutable)" --type java -n | head -50

Repository: socketio4j/netty-socketio

Length of output: 2987


🏁 Script executed:

# Check the overall project structure and type
git ls-files | grep -E "(pom|build|README)" | head -20

Repository: socketio4j/netty-socketio

Length of output: 731


Mark org.jetbrains:annotations as provided and optional to prevent transitive leakage.
The annotations are used in public APIs (e.g., RedissonStreamEventStore constructors with @NotNull and @Nullable), so they will become transitive dependencies to consumers with default scope. Mark as provided and optional so consumers don't inherit it unintentionally.

       <dependency>
           <groupId>org.jetbrains</groupId>
           <artifactId>annotations</artifactId>
           <version>26.0.2-1</version>
+          <scope>provided</scope>
+          <optional>true</optional>
       </dependency>
🤖 Prompt for AI Agents
In netty-socketio-core/pom.xml around lines 161 to 171, the
org.jetbrains:annotations dependency is currently declared with default scope
which makes it transitively available to consumers; change its declaration to
mark the dependency as provided and optional so downstream projects do not
inherit it: update the dependency entry to include <scope>provided</scope> and
<optional>true</optional> (keep groupId, artifactId and version unchanged), then
run a quick mvn dependency:tree or build to verify the annotations dependency is
no longer leaked transitively.

Comment on lines +45 to 47
import com.socketio4j.socketio.store.event.DisconnectMessage;
import com.socketio4j.socketio.store.event.EventType;
import com.socketio4j.socketio.transport.PollingTransport;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

cd netty-socketio-core && find . -name "SocketIOChannelInitializer.java" -type f

Repository: socketio4j/netty-socketio

Length of output: 140


🏁 Script executed:

# Read the file to check line count first
wc -l netty-socketio-core/src/main/java/com/socketio4j/socketio/SocketIOChannelInitializer.java

Repository: socketio4j/netty-socketio

Length of output: 162


🏁 Script executed:

# Read the relevant sections
cat -n netty-socketio-core/src/main/java/com/socketio4j/socketio/SocketIOChannelInitializer.java | sed -n '40,50p'

Repository: socketio4j/netty-socketio

Length of output: 708


🏁 Script executed:

# Read the onDisconnect method and surrounding code
cat -n netty-socketio-core/src/main/java/com/socketio4j/socketio/SocketIOChannelInitializer.java | sed -n '220,242p'

Repository: socketio4j/netty-socketio

Length of output: 1018


🏁 Script executed:

# Find EventStore interface/class
find netty-socketio-core -name "EventStore.java" -o -name "*EventStore*" -type f

Repository: socketio4j/netty-socketio

Length of output: 1165


🏁 Script executed:

# Read the EventStore interface
cat netty-socketio-core/src/main/java/com/socketio4j/socketio/store/event/EventStore.java

Repository: socketio4j/netty-socketio

Length of output: 2797


🏁 Script executed:

# Search for other publish() calls in the codebase to see if they're typically guarded
rg "\.publish\(" netty-socketio-core/src/main/java --context 2

Repository: socketio4j/netty-socketio

Length of output: 8693


🏁 Script executed:

# Check the Namespace.java context to see if publish calls are wrapped
cat -n netty-socketio-core/src/main/java/com/socketio4j/socketio/namespace/Namespace.java | grep -A 5 -B 5 "publish(EventType.BULK_LEAVE"

Repository: socketio4j/netty-socketio

Length of output: 1203


🏁 Script executed:

# Let me check if there are any tests that verify exception behavior during onDisconnect
find netty-socketio-core/src/test -name "*Test.java" -type f | head -5 | xargs grep -l "onDisconnect" 2>/dev/null | head -3

Repository: socketio4j/netty-socketio

Length of output: 258


🏁 Script executed:

# Verify once more that the current code truly has no exception handling
cat -n netty-socketio-core/src/main/java/com/socketio4j/socketio/SocketIOChannelInitializer.java | sed -n '225,234p'

Repository: socketio4j/netty-socketio

Length of output: 574


Wrap eventStore().publish() in try-catch to prevent exceptions from breaking disconnect cleanup.

The EventStore.publish() method explicitly re-throws exceptions (confirmed in EventStore.java), which means an event store failure during disconnect will abort the onDisconnect() method before the debug log completes. While cleanup operations (ackManager, authorizeHandler, and storeFactory.onDisconnect) occur before the publish call, unguarded exceptions should be avoided. Recommend guarding the publish call with try-catch and logging the failure as a warning to maintain best-effort semantics for disconnect operations.

     @Override
     public void onDisconnect(ClientHead client) {
         ackManager.onDisconnect(client);
         authorizeHandler.onDisconnect(client);
         configuration.getStoreFactory().onDisconnect(client);

-        configuration.getStoreFactory().eventStore().publish(EventType.DISCONNECT, new DisconnectMessage(client.getSessionId()));
+        try {
+            configuration.getStoreFactory()
+                    .eventStore()
+                    .publish(EventType.DISCONNECT, new DisconnectMessage(client.getSessionId()));
+        } catch (Exception e) {
+            log.warn("Failed to publish DISCONNECT event for sessionId={}", client.getSessionId(), e);
+        }

         log.debug("Client with sessionId: {} disconnected", client.getSessionId());
     }

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In
netty-socketio-core/src/main/java/com/socketio4j/socketio/SocketIOChannelInitializer.java
around lines 45-47, the call to eventStore().publish(...) is unguarded and can
re-throw exceptions that will abort disconnect handling; wrap the publish call
in a try-catch that catches Exception, log a warning including the exception and
context (e.g., the disconnect event and session id), and continue so disconnect
cleanup and the debug log still run.

Comment on lines +162 to +169
subscribe(EventType.DISPATCH, DispatchMessage.class,
m -> {
Namespace n = hub.get(m.getNamespace());
if (n != null) {
n.dispatch(m.getRoom(), m.getPacket());
}
log.debug("[PUBSUB-MC] DISPATCH {}", m.getPacket());
});
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Multi-channel DISPATCH handler missing offset attachment.

The single-channel handler (lines 88-97) attaches the offset to the packet before dispatching, but the multi-channel handler does not. This inconsistency could cause issues when using offsets with multi-channel mode.

Apply this diff to add offset attachment:

 subscribe(EventType.DISPATCH, DispatchMessage.class,
         m -> {
             Namespace n = hub.get(m.getNamespace());
+            if (m.getOffset() != null && !m.getOffset().isEmpty()) {
+                attachOffset(m.getPacket(), m.getOffset());
+            }
             if (n != null) {
                 n.dispatch(m.getRoom(), m.getPacket());
             }
             log.debug("[PUBSUB-MC] DISPATCH {}", m.getPacket());
         });
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
subscribe(EventType.DISPATCH, DispatchMessage.class,
m -> {
Namespace n = hub.get(m.getNamespace());
if (n != null) {
n.dispatch(m.getRoom(), m.getPacket());
}
log.debug("[PUBSUB-MC] DISPATCH {}", m.getPacket());
});
subscribe(EventType.DISPATCH, DispatchMessage.class,
m -> {
Namespace n = hub.get(m.getNamespace());
if (m.getOffset() != null && !m.getOffset().isEmpty()) {
attachOffset(m.getPacket(), m.getOffset());
}
if (n != null) {
n.dispatch(m.getRoom(), m.getPacket());
}
log.debug("[PUBSUB-MC] DISPATCH {}", m.getPacket());
});
🤖 Prompt for AI Agents
In
netty-socketio-core/src/main/java/com/socketio4j/socketio/store/event/BaseStoreFactory.java
around lines 162 to 169, the multi-channel DISPATCH handler logs and dispatches
the packet without attaching the message offset; modify the handler to attach
the offset to the packet the same way the single-channel handler does (retrieve
the offset from the DispatchMessage and set it on the packet before calling
Namespace.dispatch), then proceed to dispatch and log as before.

Comment on lines +42 to +44
default Long getNodeId() {
return ThreadLocalRandom.current().nextLong(Long.MAX_VALUE);
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Critical: getNodeId() returns a new random value on each invocation.

This default implementation generates a new random Long every time it's called, which defeats the purpose of a node identifier. The nodeId should be consistent for the lifetime of an EventStore instance to correctly identify the source of messages.

Implementations must override this, but relying on a broken default is error-prone. Consider removing the default or throwing UnsupportedOperationException.

     default Long getNodeId() {
-        return ThreadLocalRandom.current().nextLong(Long.MAX_VALUE);
+        throw new UnsupportedOperationException("getNodeId() must be implemented by concrete EventStore");
     }
🤖 Prompt for AI Agents
In
netty-socketio-core/src/main/java/com/socketio4j/socketio/store/event/EventStore.java
around lines 42-44, the default getNodeId() returns a new random Long on every
call which is invalid; remove the default implementation so getNodeId() becomes
an abstract method (no body) forcing concrete EventStore implementations to
provide a stable node id for their instance, or if you prefer not to break
existing code, replace the default body with "throw new
UnsupportedOperationException(...)" to avoid returning a changing value; update
method signature accordingly and run compile to find callers to implement.

Comment on lines 65 to 73
@Override
public void publish(PubSubType type, PubSubMessage msg) {
public EventStoreMode getEventStoreMode(){
return this.eventStoreMode;
}
@Override
public void publish0(EventType type, EventMessage msg) {
msg.setNodeId(nodeId);
redissonPub.getTopic(type.toString()).publish(msg);
redissonPub.getTopic(getStreamName(type)).publish(msg);
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Add null-safety for type/msg (currently NPE-prone). getStreamName(type) and msg.setNodeId(...) will blow up on null inputs.

     @Override
     public void publish0(EventType type, EventMessage msg) {
+        Objects.requireNonNull(type, "type is null");
+        Objects.requireNonNull(msg, "msg is null");
         msg.setNodeId(nodeId);
         redissonPub.getTopic(getStreamName(type)).publish(msg);
     }
🤖 Prompt for AI Agents
In
netty-socketio-core/src/main/java/com/socketio4j/socketio/store/redis_pubsub/RedissonEventStore.java
around lines 65 to 73, the publish0 method is NPE-prone because it calls
getStreamName(type) and msg.setNodeId(nodeId) without null checks; add explicit
null-safety at the start of publish0 by validating that both type and msg are
not null (throw IllegalArgumentException or a descriptive NullPointerException)
and return/abort before using them, so you avoid downstream NPEs and provide a
clear error message when callers pass null.

Comment on lines 110 to 113
@Override
public void shutdown() {
eventStore.shutdown();
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Missing shutdown of Redisson clients - potential resource leak.

The shutdown() method only delegates to eventStore.shutdown() but does not shut down the redisClient, redisPub, or redisSub instances. Comparing to the existing RedissonStoreFactory (from relevant code snippets), which explicitly shuts down all Redisson clients in order, this could lead to resource leaks.

Consider adding client shutdown similar to the existing pattern:

     @Override
     public void shutdown() {
         eventStore.shutdown();
+        
+        // Ordered hash: preserves order, no duplicates
+        Set<RedissonClient> ordered = new LinkedHashSet<>();
+        ordered.add(redisSub);
+        ordered.add(redisPub);
+        ordered.add(redisClient);
+        
+        for (RedissonClient c : ordered) {
+            if (c != null && !c.isShutdown()) {
+                try {
+                    c.shutdown();
+                    log.info("Shutdown: {}", c.getClass().getSimpleName());
+                } catch (Exception e) {
+                    log.warn("Shutdown failed: {}", c.getClass().getSimpleName(), e);
+                }
+            }
+        }
     }

Note: If clients are intentionally not shut down (e.g., externally managed), document this clearly.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
@Override
public void shutdown() {
eventStore.shutdown();
}
@Override
public void shutdown() {
eventStore.shutdown();
// Ordered hash: preserves order, no duplicates
Set<RedissonClient> ordered = new LinkedHashSet<>();
ordered.add(redisSub);
ordered.add(redisPub);
ordered.add(redisClient);
for (RedissonClient c : ordered) {
if (c != null && !c.isShutdown()) {
try {
c.shutdown();
log.info("Shutdown: {}", c.getClass().getSimpleName());
} catch (Exception e) {
log.warn("Shutdown failed: {}", c.getClass().getSimpleName(), e);
}
}
}
}

Comment on lines +794 to +812
@Test
public void testConnectAndJoinSameRoomTest() throws Exception {
io.socket.client.IO.Options opts = new io.socket.client.IO.Options();
opts.forceNew = true;

Socket a = io.socket.client.IO.socket("http://localhost:" + port1 + "?join=room1", opts);
Socket b = IO.socket("http://localhost:" + port2 + "?join=room1", opts);

a.connect();
b.connect();
CountDownLatch joinLatch = new CountDownLatch(2);
CountDownLatch connectLatch = new CountDownLatch(2);
a.on(Socket.EVENT_CONNECT, args -> {
connectLatch.countDown();
});
b.on(Socket.EVENT_CONNECT, args -> {
connectLatch.countDown();
});
assertTrue(connectLatch.await(5, TimeUnit.SECONDS), "Join timed out");
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Race condition: connect() called before listeners are registered.

In testConnectAndJoinSameRoomTest, sockets a and b call connect() at lines 802-803 before the EVENT_CONNECT listeners are registered at lines 806-811. This could cause the test to hang if the connection completes before listeners are attached.

Apply this diff to register listeners before connecting:

 Socket a = io.socket.client.IO.socket("http://localhost:" + port1 + "?join=room1", opts);
 Socket b = IO.socket("http://localhost:" + port2 + "?join=room1", opts);

-a.connect();
-b.connect();
 CountDownLatch joinLatch = new CountDownLatch(2);
 CountDownLatch connectLatch = new CountDownLatch(2);
 a.on(Socket.EVENT_CONNECT, args -> {
     connectLatch.countDown();
 });
 b.on(Socket.EVENT_CONNECT, args -> {
     connectLatch.countDown();
 });
+a.connect();
+b.connect();
 assertTrue(connectLatch.await(5, TimeUnit.SECONDS), "Join timed out");
🤖 Prompt for AI Agents
In
netty-socketio-core/src/test/java/com/socketio4j/socketio/integration/DistributedCommonTest.java
around lines 794 to 812, the test currently calls a.connect() and b.connect()
before attaching EVENT_CONNECT listeners which creates a race where connections
may succeed before handlers are registered; move the a.on(...) and b.on(...)
listener registrations (and any related latch setup) to before calling
a.connect() and b.connect() so listeners are in place prior to initiating the
connections, then call connect() for both sockets and await the latches as
before.

Comment on lines 54 to 103
@BeforeAll
public void setup() throws Exception {
REDIS_CONTAINER.start();

String redisURL = "redis://" + REDIS_CONTAINER.getHost() + ":" + REDIS_CONTAINER.getRedisPort();
redisClient1 = Redisson.create(redisConfig(redisURL));
redisClient2 = Redisson.create(redisConfig(redisURL));
// ---------- NODE 1 ----------
Configuration cfg1 = new Configuration();
cfg1.setHostname("127.0.0.1");
cfg1.setPort(findAvailablePort());

cfg1.setStoreFactory(new RedissonStoreFactory(
redisClient1, PublishConfig.allUnreliable(), EventStoreMode.SINGLE_CHANNEL
));

node1 = new SocketIOServer(cfg1);
node1.addEventListener("join-room", String.class, (c, room, ack) -> {
c.joinRoom(room);
c.sendEvent("join-ok", "OK");
});
node1.addEventListener("leave-room", String.class, (c, room, ack) -> {
c.leaveRoom(room);
c.sendEvent("leave-ok", "OK");
});
node1.start();
port1 = cfg1.getPort();

// ---------- NODE 2 ----------
Configuration cfg2 = new Configuration();
cfg2.setHostname("127.0.0.1");
cfg2.setPort(findAvailablePort());

cfg2.setStoreFactory(new RedissonStoreFactory(
redisClient2, PublishConfig.allUnreliable(), EventStoreMode.SINGLE_CHANNEL));

node2 = new SocketIOServer(cfg2);
node2.addEventListener("join-room", String.class, (c, room, ack) -> {
c.joinRoom(room);
c.sendEvent("join-ok", "OK");
});
node2.addEventListener("leave-room", String.class, (c, room, ack) -> {
c.leaveRoom(room);
c.sendEvent("leave-ok", "OK");
});
node2.start();
port2 = cfg2.getPort();

//Thread.sleep(600);
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Make server startup deterministic (remove “sleep” temptation, reduce CI flakes).
The commented Thread.sleep(600) suggests timing sensitivity; better to actively wait until both nodes are accepting connections / adapters subscribed.

Suggested direction (pick one):

  • Poll-connect a socket.io client to node1/node2 until success (with timeout), then run assertions.
  • Use an await utility (e.g., Awaitility) to wait for a known-ready condition (e.g., namespace available, first ping/pong, etc.).

(Keeping this test stable matters a lot since it’s validating distributed behavior.)

🤖 Prompt for AI Agents
In
netty-socketio-core/src/test/java/com/socketio4j/socketio/integration/DistributedRedissonPubSubSingleChannelUnreliableTest.java
around lines 54 to 103, replace the commented Thread.sleep with a deterministic
wait: actively wait until both node1 and node2 are ready and accepting
connections instead of sleeping. Implement a short polling loop or use
Awaitility to attempt connecting a socket.io client (or checking a known-ready
condition such as server.isStarted(), namespace presence, or Redisson
subscription count) against each node with a sensible timeout (e.g., 5–10s) and
fail the setup if the wait times out; once both nodes respond, proceed with the
test. Ensure the wait checks are granular (retry interval ~100-200ms) and
time-bounded to avoid CI flakes.

Comment on lines +139 to 144
try (InstanceHandle<ExceptionListener> exceptionListenerInstanceHandle = Arc.container().instance(ExceptionListener.class)) {
if (exceptionListenerInstanceHandle != null && exceptionListenerInstanceHandle.isAvailable()) {
log.info("Netty socket-io server configuration uses ExceptionListener: {}", exceptionListenerInstanceHandle.get().getClass().getName());
configuration.setExceptionListener(exceptionListenerInstanceHandle.get());
}
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Bean lifecycle issue: try-with-resources destroys dependent-scoped beans while configuration retains references.

The try-with-resources pattern closes the InstanceHandle after retrieving the bean, but the configuration object stores a reference to that bean and is used later (returned at line 167). In Quarkus Arc, InstanceHandle.close() destroys dependent-scoped beans. If ExceptionListener is @Dependent, the configuration will hold a reference to a destroyed bean, leading to undefined behavior or NPEs when the configuration is used.

Solution 1 (recommended): Remove try-with-resources since you're keeping the bean reference:

-        try (InstanceHandle<ExceptionListener> exceptionListenerInstanceHandle = Arc.container().instance(ExceptionListener.class)) {
+        InstanceHandle<ExceptionListener> exceptionListenerInstanceHandle = Arc.container().instance(ExceptionListener.class);
+        {
             if (exceptionListenerInstanceHandle != null && exceptionListenerInstanceHandle.isAvailable()) {
                 log.info("Netty socket-io server configuration uses ExceptionListener: {}", exceptionListenerInstanceHandle.get().getClass().getName());
                 configuration.setExceptionListener(exceptionListenerInstanceHandle.get());
             }
         }

Solution 2: Document and enforce that these beans must be @ApplicationScoped or @Singleton:

// Add comment before block
// Note: Assumes ExceptionListener bean is @ApplicationScoped or @Singleton.
// @Dependent scope would cause the bean to be destroyed when the handle closes.
🤖 Prompt for AI Agents
In
netty-socketio-quarkus/netty-socketio-quarkus-runtime/src/main/java/com/socketio4j/socketio/quarkus/recorder/NettySocketIOConfigRecorder.java
around lines 139-144, the try-with-resources closes the InstanceHandle which
will destroy @Dependent-scoped beans while the configuration retains a
reference; remove the try-with-resources pattern and do not close the
InstanceHandle (or otherwise avoid calling close()) when you store the bean
reference in configuration so dependent beans are not destroyed, or
alternatively document/enforce that ExceptionListener must be
@ApplicationScoped/@Singleton if you intend to close the handle.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

♻️ Duplicate comments (3)
netty-socketio-core/src/main/java/com/socketio4j/socketio/store/redis_pubsub/RedissonEventStore.java (2)

72-79: Add null-safety for type and msg parameters (NPE-prone).

getStreamName(type) and msg.setNodeId(nodeId) will throw NPE if either parameter is null. This issue was flagged in a previous review but remains unresolved.

Apply this diff:

 @Override
 public void publish0(EventType type, EventMessage msg) {
+    Objects.requireNonNull(type, "type cannot be null");
+    Objects.requireNonNull(msg, "msg cannot be null");
     msg.setNodeId(nodeId);
     RTopic topic = activePubTopics.computeIfAbsent(type, k -> {

82-91: Fix race condition: computeIfAbsent(...).add(regId) can lose tracking during concurrent unsubscribe0.

The two-step operation (computeIfAbsent then add) is not atomic. If unsubscribe0 removes the queue between these steps, the regId will be added to an orphaned queue that's no longer in the map, leaking the listener registration. This issue was flagged in a previous review but remains unresolved.

Apply this diff to make the operation atomic:

 public <T extends EventMessage> void subscribe0(EventType type, final EventListener<T> listener, Class<T> clazz) {
+    Objects.requireNonNull(type, "type cannot be null");
+    Objects.requireNonNull(listener, "listener cannot be null");
+    Objects.requireNonNull(clazz, "clazz cannot be null");
     RTopic topic = redissonSub.getTopic(getStreamName(type));
     int regId = topic.addListener(clazz, (channel, msg) -> {
         if (!nodeId.equals(msg.getNodeId())) {
             listener.onMessage(msg);
         }
     });
     activeSubTopics.put(regId, topic);
-    map.computeIfAbsent(type, k -> new ConcurrentLinkedQueue<>()).add(regId);
+    map.compute(type, (k, q) -> {
+        if (q == null) {
+            q = new ConcurrentLinkedQueue<>();
+        }
+        q.add(regId);
+        return q;
+    });
 }
netty-socketio-core/src/main/java/com/socketio4j/socketio/store/redis_stream/RedissonStreamEventStore.java (1)

122-169: Constructor duplicates Builder logic.

This explicit constructor duplicates the same initialization logic found in the Builder-based constructor (lines 63-110), including null checks, default value assignment, executor creation, and trim scheduling. This was already flagged in a previous review.

🧹 Nitpick comments (3)
netty-socketio-core/src/main/java/com/socketio4j/socketio/store/hazelcast/HazelcastEventStore.java (1)

78-83: Consider renaming method to reflect actual purpose.

The method is named getRingBufferName but returns topic names for Hazelcast ITopic, not ring buffers. Consider renaming to getTopicName for clarity.

-private String getRingBufferName(EventType type) {
+private String getTopicName(EventType type) {
     if (EventStoreMode.SINGLE_CHANNEL.equals(eventStoreMode)) {
         return EventType.ALL_SINGLE_CHANNEL.name();
     }
     return type.name();
 }

Update all call sites accordingly (lines 72, 92, 120).

netty-socketio-core/src/main/java/com/socketio4j/socketio/store/redis_stream/RedissonStreamEventStore.java (2)

47-48: Consider consolidating redissonPub and redissonSub fields.

Both fields are always assigned to the same RedissonClient instance in all constructors. If there's no plan to support separate publish/subscribe clients, consider using a single redissonClient field to reduce redundancy.


163-163: Simplify initial delay expression.

The expression Duration.ofSeconds(1).getSeconds() is unnecessarily verbose when you can use the literal 1L directly.

Apply this diff:

-                    Duration.ofSeconds(1).getSeconds(),
+                    1L,
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 3f49a03 and 27292c1.

📒 Files selected for processing (4)
  • netty-socketio-core/src/main/java/com/socketio4j/socketio/store/hazelcast/HazelcastEventStore.java (1 hunks)
  • netty-socketio-core/src/main/java/com/socketio4j/socketio/store/hazelcast_ringbuffer/HazelcastRingBufferEventStore.java (1 hunks)
  • netty-socketio-core/src/main/java/com/socketio4j/socketio/store/redis_pubsub/RedissonEventStore.java (1 hunks)
  • netty-socketio-core/src/main/java/com/socketio4j/socketio/store/redis_stream/RedissonStreamEventStore.java (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • netty-socketio-core/src/main/java/com/socketio4j/socketio/store/hazelcast_ringbuffer/HazelcastRingBufferEventStore.java
🧰 Additional context used
🧬 Code graph analysis (2)
netty-socketio-core/src/main/java/com/socketio4j/socketio/store/hazelcast/HazelcastEventStore.java (1)
netty-socketio-core/src/main/java/com/socketio4j/socketio/store/event/EventMessage.java (1)
  • EventMessage (21-44)
netty-socketio-core/src/main/java/com/socketio4j/socketio/store/redis_pubsub/RedissonEventStore.java (1)
netty-socketio-core/src/main/java/com/socketio4j/socketio/store/event/EventMessage.java (1)
  • EventMessage (21-44)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (6)
  • GitHub Check: build (21) / build
  • GitHub Check: build (17) / build
  • GitHub Check: build (25) / build
  • GitHub Check: qodana
  • GitHub Check: Analyze (java-kotlin)
  • GitHub Check: Analyze (java-kotlin)
🔇 Additional comments (13)
netty-socketio-core/src/main/java/com/socketio4j/socketio/store/hazelcast/HazelcastEventStore.java (3)

52-65: Constructor properly validates required dependencies.

The null checks for hazelcastPub, hazelcastSub, and nodeId are appropriate, and defaulting eventStoreMode to MULTI_CHANNEL when null is a sensible fallback.


106-123: LGTM: Proper cleanup with error handling.

The unsubscribe logic correctly handles null/empty cases and wraps listener removal in try-catch to prevent partial failures from blocking cleanup.


126-132: LGTM: Shutdown properly cleans up without terminating clients.

The shutdown sequence unsubscribes all event types, clears internal state, and correctly avoids shutting down the Hazelcast clients (which may be shared).

netty-socketio-core/src/main/java/com/socketio4j/socketio/store/redis_pubsub/RedissonEventStore.java (4)

53-65: Constructor properly validates required dependencies.

The null checks for redissonPub, redissonSub, and nodeId are appropriate, and defaulting eventStoreMode to MULTI_CHANNEL when null is a sensible fallback.


92-97: LGTM: Stream name resolution correctly handles single-channel mode.

The method properly returns the unified channel name for SINGLE_CHANNEL mode or the event-type-specific name otherwise.


99-116: LGTM: Proper cleanup with error handling.

The unsubscribe logic correctly handles null/empty cases and wraps listener removal in try-catch to prevent partial failures from blocking cleanup.


119-124: LGTM: Shutdown properly cleans up without terminating clients.

The shutdown sequence unsubscribes all event types and clears internal state without shutting down the Redisson clients (which may be shared).

netty-socketio-core/src/main/java/com/socketio4j/socketio/store/redis_stream/RedissonStreamEventStore.java (6)

170-183: LGTM!

The mode-specific trimming logic correctly handles SINGLE_CHANNEL vs. MULTI_CHANNEL modes, and exception handling is appropriate.


185-198: LGTM!

The use of trimNonStrictAsync with noLimit() implements efficient approximate MAXLEN trimming. Exception handling and logging are appropriate.


210-217: LGTM!

The message origin tracking via nodeId assignment and thread-safe lazy topic creation using computeIfAbsent are correctly implemented.


220-232: LGTM!

The nodeId filtering prevents message echo-back, and dual registration tracking (in activeSubTopics and map) properly supports cleanup during unsubscription.


242-261: LGTM!

The unsubscription logic correctly removes registrations, removes listeners, and handles exceptions gracefully.


272-311: LGTM!

The Builder pattern is correctly implemented with required and optional fields, following standard fluent API conventions.

Comment on lines 68 to 77
public void publish0(EventType type, EventMessage msg) {
msg.setNodeId(nodeId);

ITopic<EventMessage> topic = activePubTopics.computeIfAbsent(type, k -> {
String topicName = getRingBufferName(k);
return hazelcastPub.getTopic(topicName);
});

topic.publish(msg);
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Add null-safety for type and msg parameters (NPE-prone).

getRingBufferName(type) and msg.setNodeId(nodeId) will throw NPE if either parameter is null. Add explicit validation at the method entry.

Apply this diff:

 @Override
 public void publish0(EventType type, EventMessage msg) {
+    Objects.requireNonNull(type, "type cannot be null");
+    Objects.requireNonNull(msg, "msg cannot be null");
     msg.setNodeId(nodeId);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
public void publish0(EventType type, EventMessage msg) {
msg.setNodeId(nodeId);
ITopic<EventMessage> topic = activePubTopics.computeIfAbsent(type, k -> {
String topicName = getRingBufferName(k);
return hazelcastPub.getTopic(topicName);
});
topic.publish(msg);
}
public void publish0(EventType type, EventMessage msg) {
Objects.requireNonNull(type, "type cannot be null");
Objects.requireNonNull(msg, "msg cannot be null");
msg.setNodeId(nodeId);
ITopic<EventMessage> topic = activePubTopics.computeIfAbsent(type, k -> {
String topicName = getRingBufferName(k);
return hazelcastPub.getTopic(topicName);
});
topic.publish(msg);
}
🤖 Prompt for AI Agents
In
netty-socketio-core/src/main/java/com/socketio4j/socketio/store/hazelcast/HazelcastEventStore.java
around lines 68–77, add explicit null checks for the method parameters so
getRingBufferName(type) and msg.setNodeId(nodeId) cannot NPE: validate that both
'type' and 'msg' are non-null at the start of publish0 (use
Objects.requireNonNull(...) or explicit if-checks) and throw a clear
NullPointerException/IllegalArgumentException with a message if either is null;
only after these checks proceed to computeIfAbsent/getTopic and call
msg.setNodeId(nodeId) and topic.publish(msg).

Comment on lines 90 to 103
public <T extends EventMessage> void subscribe0(EventType type, final EventListener<T> listener, Class<T> clazz) {

ITopic<T> topic = hazelcastSub.getTopic(getRingBufferName(type));

UUID regId = topic.addMessageListener(msg -> {
if (!nodeId.equals(msg.getMessageObject().getNodeId())) {
listener.onMessage(msg.getMessageObject());
}
});
activeSubTopics.put(regId, topic);

listenerMap.computeIfAbsent(type, k -> new ConcurrentLinkedQueue<>())
.add(regId);
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Fix race condition: computeIfAbsent(...).add(regId) can lose tracking during concurrent unsubscribe0.

The two-step operation (computeIfAbsent then add) is not atomic. If unsubscribe0 removes the queue between these steps, the regId will be added to an orphaned queue that's no longer in the map, leaking the listener registration.

Apply this diff to make the operation atomic:

 public <T extends EventMessage> void subscribe0(EventType type, final EventListener<T> listener, Class<T> clazz) {
+    Objects.requireNonNull(type, "type cannot be null");
+    Objects.requireNonNull(listener, "listener cannot be null");
+    Objects.requireNonNull(clazz, "clazz cannot be null");
 
     ITopic<T> topic = hazelcastSub.getTopic(getRingBufferName(type));
 
     UUID regId = topic.addMessageListener(msg -> {
         if (!nodeId.equals(msg.getMessageObject().getNodeId())) {
             listener.onMessage(msg.getMessageObject());
         }
     });
     activeSubTopics.put(regId, topic);
 
-    listenerMap.computeIfAbsent(type, k -> new ConcurrentLinkedQueue<>())
-            .add(regId);
+    listenerMap.compute(type, (k, q) -> {
+        if (q == null) {
+            q = new ConcurrentLinkedQueue<>();
+        }
+        q.add(regId);
+        return q;
+    });
 }
🤖 Prompt for AI Agents
In
netty-socketio-core/src/main/java/com/socketio4j/socketio/store/hazelcast/HazelcastEventStore.java
around lines 90 to 103, the current two-step computeIfAbsent(...).add(regId) is
racy because unsubscribe0 may remove the queue between creation and add; change
this to an atomic map.compute(...) (or equivalent synchronized block) that
creates the ConcurrentLinkedQueue if null, adds regId into that queue, and
returns the queue in a single atomic operation so the registration ID cannot be
appended to a queue that was concurrently removed.

Comment on lines 78 to 84
if (b.streamNamePrefix == null || b.streamNamePrefix.isEmpty()) {
this.streamNamePrefix =
DEFAULT_STREAM_NAME_PREFIX;
} else {
this.streamNamePrefix =
b.streamNamePrefix;
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Inconsistent default value logging behavior between constructors.

The explicit constructor (lines 131-141) logs warnings when applying default values for streamNamePrefix and streamMaxLength, but the Builder constructor (lines 78-92) silently applies the same defaults without logging. This inconsistency can confuse users about when defaults are applied.

Consider either:

  1. Adding the same warning logs to the Builder constructor path, or
  2. Removing the warnings from the explicit constructor if silent defaults are acceptable.

Also applies to: 86-92, 131-141

🤖 Prompt for AI Agents
In
netty-socketio-core/src/main/java/com/socketio4j/socketio/store/redis_stream/RedissonStreamEventStore.java
around lines 78-92 and 131-141, the Builder-based constructor silently applies
defaults while the explicit constructor logs warnings; make behavior consistent
by adding the same warning logs in the Builder path when defaulting
streamNamePrefix and streamMaxLength (or alternatively remove the warnings from
the explicit ctor if you prefer silent defaults) — implement the chosen approach
by using the same logger and message text/format used in the explicit
constructor at the Builder assignments for streamNamePrefix and streamMaxLength,
and ensure you do not produce duplicate messages when both constructors run.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 6

♻️ Duplicate comments (9)
netty-socketio-core/src/main/java/com/socketio4j/socketio/store/redis_stream/RedissonStreamStoreFactory.java (1)

69-72: Missing shutdown of RedissonClient - potential resource leak.

The shutdown() method only delegates to eventStore.shutdown() but does not shut down the redissonClient. If this factory owns the client lifecycle, this will leak Redis connections.

If the redissonClient is externally managed, document this clearly. Otherwise, consider shutting it down:

     @Override
     public void shutdown() {
         eventStore.shutdown();
+        if (redissonClient != null && !redissonClient.isShutdown()) {
+            try {
+                redissonClient.shutdown();
+            } catch (Exception e) {
+                log.warn("Failed to shutdown RedissonClient", e);
+            }
+        }
     }
netty-socketio-core/src/test/java/com/socketio4j/socketio/integration/DistributedRedissonPubSubSingleChannelUnreliableTest.java (1)

103-105: Consider deterministic readiness check instead of sleep.

The commented Thread.sleep(600) suggests timing sensitivity. For CI stability, consider using a deterministic wait (e.g., Awaitility or polling for server readiness) rather than arbitrary sleeps.

netty-socketio-core/src/test/java/com/socketio4j/socketio/integration/DistributedHazelcastRingBufferSingleChannelTest.java (1)

24-38: Remove unused Redisson imports - copy-paste artifacts.

This is a Hazelcast test but includes unused Redis-related imports. This was flagged in a previous review.

 import org.junit.jupiter.api.AfterAll;
 import org.junit.jupiter.api.BeforeAll;
 import org.junit.jupiter.api.TestInstance;
-import org.redisson.Redisson;
-import org.redisson.api.RedissonClient;
-import org.redisson.config.Config;

 import com.hazelcast.client.HazelcastClient;
 import com.hazelcast.client.config.ClientConfig;
 import com.hazelcast.core.HazelcastInstance;
 import com.socketio4j.socketio.Configuration;
 import com.socketio4j.socketio.SocketIOServer;
 import com.socketio4j.socketio.store.CustomizedHazelcastContainer;
-import com.socketio4j.socketio.store.CustomizedRedisContainer;
 import com.socketio4j.socketio.store.event.EventStoreMode;
 import com.socketio4j.socketio.store.hazelcast_ringbuffer.HazelcastRingBufferEventStore;
 import com.socketio4j.socketio.store.hazelcast_ringbuffer.HazelcastRingBufferStoreFactory;
-import com.socketio4j.socketio.store.redis_stream.RedissonStreamStoreFactory;
netty-socketio-core/src/main/java/com/socketio4j/socketio/store/redis_stream/RedissonStreamEventStore.java (1)

243-251: Redundant shutdown() call before shutdownNow().

Line 244 calls trimExecutor.shutdown() and line 247 immediately calls trimExecutor.shutdownNow(). The shutdown() call is redundant because shutdownNow() already handles stopping the executor.

netty-socketio-core/src/main/java/com/socketio4j/socketio/store/hazelcast/HazelcastEventStore.java (3)

85-95: Add null-safety for type and msg parameters.

getTopicName(type) and msg.setNodeId(nodeId) will throw NPE if either parameter is null. Add explicit validation at the method entry.


119-121: Race condition: computeIfAbsent(...).add(regId) can lose tracking during concurrent unsubscribe0.

The two-step operation is not atomic. If unsubscribe0 removes the queue between these steps, the regId will be added to an orphaned queue, leaking the listener registration.


191-197: Incorrect error message references wrong field name.

The error message says "ringBufferNamePrefix" but this method sets topicNamePrefix. This appears to be a copy-paste error from HazelcastRingBufferEventStore.

Apply this diff:

 public HazelcastEventStore.Builder topicNamePrefix(@NotNull String prefix) {
     if (prefix.isEmpty()) {
-        throw new IllegalArgumentException("ringBufferNamePrefix cannot be empty");
+        throw new IllegalArgumentException("topicNamePrefix cannot be empty");
     }
     this.topicNamePrefix = prefix;
     return this;
 }
netty-socketio-core/src/main/java/com/socketio4j/socketio/store/redis_pubsub/RedissonEventStore.java (2)

89-97: Add null-safety for type and msg parameters.

getStreamName(type) and msg.setNodeId(nodeId) will throw NPE if either parameter is null. Add explicit validation at the method entry.


99-109: Race condition: computeIfAbsent(...).add(regId) can lose tracking during concurrent unsubscribe0.

The two-step operation is not atomic. If unsubscribe0 removes the queue between these steps, the regId will be added to an orphaned queue, leaking the listener registration.

🧹 Nitpick comments (22)
netty-socketio-core/src/main/java/com/socketio4j/socketio/store/redis_stream/RedissonStreamStoreFactory.java (1)

19-34: Remove unused imports.

Several imports are unused in this file:

  • Duration (line 19)
  • Redisson (line 24)
  • EventStoreMode (line 32)
-import java.time.Duration;
 import java.util.Map;
 import java.util.Objects;
 import java.util.UUID;

-import org.redisson.Redisson;
 import org.redisson.api.RedissonClient;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;

 import com.socketio4j.socketio.store.Store;
 import com.socketio4j.socketio.store.event.BaseStoreFactory;
 import com.socketio4j.socketio.store.event.EventStore;
-import com.socketio4j.socketio.store.event.EventStoreMode;
 import com.socketio4j.socketio.store.redis_pubsub.RedissonStore;
netty-socketio-core/src/test/java/com/socketio4j/socketio/integration/DistributedRedissonStreamMultiChannelTest.java (1)

32-34: Remove unused import.

RedissonEventStore is imported but not used in this test (it uses RedissonStreamEventStore instead).

 import com.socketio4j.socketio.store.event.EventStoreMode;
-import com.socketio4j.socketio.store.redis_pubsub.RedissonEventStore;
 import com.socketio4j.socketio.store.redis_stream.RedissonStreamEventStore;
 import com.socketio4j.socketio.store.redis_stream.RedissonStreamStoreFactory;
netty-socketio-core/src/test/java/com/socketio4j/socketio/integration/DistributedHazelcastRingBufferSingleChannelTest.java (1)

56-58: Fix misleading comment - this is Hazelcast, not Redis.

The comment says "Redis + Node Setup" but this test uses Hazelcast.

     // -------------------------------------------
-    // Redis + Node Setup
+    // Hazelcast + Node Setup
     // -------------------------------------------
netty-socketio-core/src/test/java/com/socketio4j/socketio/integration/DistributedRedissonPubSubMultiChannelUnReliableTest.java (3)

40-50: Potential flaky port allocation (TOCTOU) in findAvailablePort().
ServerSocket(0) + close can lose the port before SocketIOServer binds, especially under parallel CI. Consider using a port utility (if available) or letting the server bind 0 directly (if supported) and reading the actual bound port afterwards. Also, the “unencrypted socket” static-analysis hint here is a false positive: this socket is only for port discovery, not data transport.


55-105: Test “unreliable” semantics aren’t apparent from configuration.
Class name says UnReliable, but the event store is only configured with EventStoreMode.MULTI_CHANNEL. If “unreliable” is meant to disable retries/acks, please wire that explicitly (or rename the test to match actual behavior).


113-131: Make teardown exception-safe to avoid resource leaks on partial failures.
If node1.stop() (or any shutdown) throws, the remaining resources won’t be cleaned up, which can cascade into later test failures. Consider guarding each close with try/catch (or a small helper) to ensure all resources attempt shutdown.

netty-socketio-core/src/main/java/com/socketio4j/socketio/store/hazelcast_ringbuffer/HazelcastRingBufferStoreFactory.java (1)

51-79: LGTM: clean factory wrapper around Hazelcast-backed Store + EventStore.
One small API footgun: since HazelcastRingBufferEventStore is passed in, consider documenting (or asserting) that the eventStore was built against the same HazelcastInstance as hazelcastClient, otherwise you can end up storing session data in one cluster and publishing events on another.

netty-socketio-core/src/test/java/com/socketio4j/socketio/integration/DistributedHazelcastPubSubSingleChannelUnreliableTest.java (3)

46-53: Potential flaky port allocation (TOCTOU) in findAvailablePort().
Same issue as other distributed tests: the chosen port can be taken before bind. If Configuration#setPort(0) works, that’s usually the least flaky option.


55-120: Remove leftover Redis-specific helper/comment to reduce confusion.
This is a Hazelcast single-channel test, but it still contains “Redis + Node Setup” and a Redis config helper. Suggest trimming that to keep the scenario clear.

-    // -------------------------------------------
-    // Redis + Node Setup
-    // -------------------------------------------
+    // -------------------------------------------
+    // Hazelcast + Node Setup
+    // -------------------------------------------
@@
-    private Config redisConfig(String url) {
-        Config c = new Config();
-        c.useSingleServer().setAddress(url);
-        return c;
-    }
+    // (no Redis helpers in this test)

121-139: Make teardown exception-safe to avoid leaks impacting later tests.
Wrap each stop/shutdown in try/catch so one failure doesn’t prevent cleanup of the rest.

netty-socketio-core/src/test/java/com/socketio4j/socketio/integration/DistributedHazelcastPubSubMultiChannelUnReliableTest.java (3)

45-49: Potential flaky port allocation (TOCTOU) in findAvailablePort().
Consider letting the server bind to port 0 (if supported) or using a stable port allocator utility to reduce CI flakes.


111-129: Make teardown exception-safe to avoid cross-test interference.
Guard each stop/shutdown so all resources attempt cleanup even if one throws.


36-38: Consider normalizing UnReliable vs Unreliable naming across tests.
This is minor, but it helps discoverability and consistency.

netty-socketio-core/src/main/java/com/socketio4j/socketio/store/redis_pubsub/RedissonStoreFactory.java (1)

49-77: Document/guard redisClienteventStore pairing to prevent misconfiguration.
Since callers can pass any EventStore, it’s easy to accidentally use a store factory whose session data goes to one Redis while events are published on another. Consider documenting that they must be created from the same RedissonClient (or add a defensive check if the event store exposes its client). Also: it’s good that shutdown() doesn’t implicitly shutdown redisClient—callers clearly own the client lifecycle.

netty-socketio-core/src/main/java/com/socketio4j/socketio/store/hazelcast/HazelcastStoreFactory.java (2)

31-32: Remove unused imports.

EventStoreMode and PublishConfig are imported but not used in this class.

Apply this diff:

-import com.socketio4j.socketio.store.event.EventStoreMode;
-import com.socketio4j.socketio.store.event.PublishConfig;

41-41: Remove unused logger field.

The log field is declared but never used in this class.

Apply this diff:

-    private static final Logger log = LoggerFactory.getLogger(HazelcastStoreFactory.class);
-
     private final HazelcastInstance hazelcastClient;

If logging is intended for future use, consider adding it when needed.

netty-socketio-core/src/test/java/com/socketio4j/socketio/integration/DistributedHazelcastRingBufferMultiChannelTest.java (2)

24-26: Remove unused imports.

The Redisson-related imports (Redisson, RedissonClient, Config, CustomizedRedisContainer, RedissonStreamStoreFactory) and HazelcastEventStore are not used in this test class.

Apply this diff:

 import org.junit.jupiter.api.AfterAll;
 import org.junit.jupiter.api.BeforeAll;
 import org.junit.jupiter.api.TestInstance;
-import org.redisson.Redisson;
-import org.redisson.api.RedissonClient;
-import org.redisson.config.Config;

 import com.hazelcast.client.HazelcastClient;
 import com.hazelcast.client.config.ClientConfig;
 import com.hazelcast.core.HazelcastInstance;
 import com.socketio4j.socketio.Configuration;
 import com.socketio4j.socketio.SocketIOServer;
 import com.socketio4j.socketio.store.CustomizedHazelcastContainer;
-import com.socketio4j.socketio.store.CustomizedRedisContainer;
 import com.socketio4j.socketio.store.event.EventStoreMode;
-import com.socketio4j.socketio.store.hazelcast.HazelcastEventStore;
 import com.socketio4j.socketio.store.hazelcast_ringbuffer.HazelcastRingBufferEventStore;
 import com.socketio4j.socketio.store.hazelcast_ringbuffer.HazelcastRingBufferStoreFactory;
-import com.socketio4j.socketio.store.redis_stream.RedissonStreamStoreFactory;

Also applies to: 34-39


57-59: Fix misleading comment.

The comment says "Redis + Node Setup" but this test sets up Hazelcast nodes.

Apply this diff:

-    // -------------------------------------------
-    // Redis + Node Setup
-    // -------------------------------------------
+    // -------------------------------------------
+    // Hazelcast + Node Setup
+    // -------------------------------------------
netty-socketio-core/src/test/java/com/socketio4j/socketio/store/event/HazelcastRingBufferEventStoreTest.java (1)

28-29: Update outdated Javadoc.

The Javadoc still references "HazelcastPubSubStore" which no longer exists.

Apply this diff:

 /**
- * Test class for HazelcastPubSubStore using testcontainers
+ * Test class for HazelcastRingBufferEventStore using testcontainers
  */
netty-socketio-core/src/test/java/com/socketio4j/socketio/store/RedissonStreamStoreFactoryTest.java (1)

106-111: Update assertion messages to match EventStore terminology.

The assertion messages still reference the old PubSubStore terminology while the code now uses EventStore. This inconsistency can cause confusion during test failures.

Apply this diff:

 public void testRedissonEventStore() {
     EventStore eventStore = storeFactory.eventStore();
     
-    assertNotNull(eventStore, "PubSubStore should not be null");
-    assertTrue(eventStore instanceof RedissonEventStore, "PubSubStore should be RedissonPubSubStore");
+    assertNotNull(eventStore, "EventStore should not be null");
+    assertTrue(eventStore instanceof RedissonEventStore, "EventStore should be RedissonEventStore");
 }
netty-socketio-core/src/main/java/com/socketio4j/socketio/store/redis_stream/RedissonStreamEventStore.java (1)

109-123: Executor created unconditionally even when trimming is disabled.

The ScheduledExecutorService is always instantiated (lines 109-114), but only used when trimEvery != null. This wastes a thread when trimming is not configured.

Consider lazy initialization:

-        ScheduledExecutorService executorService = Executors.newSingleThreadScheduledExecutor(r -> {
-            Thread t = new Thread(r, "socketio4j-redis-stream-trimmer");
-            t.setDaemon(true);
-            return t;
-        });
-        this.trimExecutor = executorService;
         this.trimEvery = trimEvery;
         if (trimEvery != null) {
+            ScheduledExecutorService executorService = Executors.newSingleThreadScheduledExecutor(r -> {
+                Thread t = new Thread(r, "socketio4j-redis-stream-trimmer");
+                t.setDaemon(true);
+                return t;
+            });
+            this.trimExecutor = executorService;
             executorService.scheduleAtFixedRate(
                     this::trimAllReliableStreams,
                     Duration.ofSeconds(1).getSeconds(),
                     trimEvery.getSeconds(),
                     TimeUnit.SECONDS
             );
+        } else {
+            this.trimExecutor = null;
         }

Then guard shutdown accordingly:

     public void shutdown0() {
-        trimExecutor.shutdown();
+        if (trimExecutor != null) {
+            trimExecutor.shutdownNow();
+        }
         Arrays.stream(EventType.values()).forEach(this::unsubscribe);
         map.clear();
-        trimExecutor.shutdownNow();
netty-socketio-core/src/main/java/com/socketio4j/socketio/store/redis_pubsub/RedissonEventStore.java (1)

110-115: Missing topic name prefix support for namespace isolation.

Unlike RedissonStreamEventStore (which has streamNamePrefix) and HazelcastEventStore (which has topicPrefix), this implementation uses raw EventType.name() without any configurable prefix. This can cause topic collisions when multiple applications share the same Redis instance.

Consider adding a topicNamePrefix field and Builder method for consistency with sibling implementations:

+    private static final String DEFAULT_TOPIC_NAME_PREFIX = "SOCKETIO4J:";
+    private final String topicNamePrefix;
 
     // In constructor:
+    this.topicNamePrefix = topicNamePrefix != null ? topicNamePrefix : DEFAULT_TOPIC_NAME_PREFIX;
 
     private String getStreamName(EventType type) {
         if (EventStoreMode.SINGLE_CHANNEL.equals(eventStoreMode)) {
-            return  EventType.ALL_SINGLE_CHANNEL.name();
+            return topicNamePrefix + EventType.ALL_SINGLE_CHANNEL.name();
         }
-        return type.name();
+        return topicNamePrefix + type.name();
     }
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 27292c1 and 8977420.

📒 Files selected for processing (20)
  • netty-socketio-core/src/main/java/com/socketio4j/socketio/store/hazelcast/HazelcastEventStore.java (1 hunks)
  • netty-socketio-core/src/main/java/com/socketio4j/socketio/store/hazelcast/HazelcastStoreFactory.java (1 hunks)
  • netty-socketio-core/src/main/java/com/socketio4j/socketio/store/hazelcast_ringbuffer/HazelcastRingBufferEventStore.java (1 hunks)
  • netty-socketio-core/src/main/java/com/socketio4j/socketio/store/hazelcast_ringbuffer/HazelcastRingBufferStoreFactory.java (1 hunks)
  • netty-socketio-core/src/main/java/com/socketio4j/socketio/store/redis_pubsub/RedissonEventStore.java (1 hunks)
  • netty-socketio-core/src/main/java/com/socketio4j/socketio/store/redis_pubsub/RedissonStoreFactory.java (1 hunks)
  • netty-socketio-core/src/main/java/com/socketio4j/socketio/store/redis_stream/RedissonStreamEventStore.java (1 hunks)
  • netty-socketio-core/src/main/java/com/socketio4j/socketio/store/redis_stream/RedissonStreamStoreFactory.java (1 hunks)
  • netty-socketio-core/src/test/java/com/socketio4j/socketio/integration/DistributedHazelcastPubSubMultiChannelUnReliableTest.java (1 hunks)
  • netty-socketio-core/src/test/java/com/socketio4j/socketio/integration/DistributedHazelcastPubSubSingleChannelUnreliableTest.java (1 hunks)
  • netty-socketio-core/src/test/java/com/socketio4j/socketio/integration/DistributedHazelcastRingBufferMultiChannelTest.java (1 hunks)
  • netty-socketio-core/src/test/java/com/socketio4j/socketio/integration/DistributedHazelcastRingBufferSingleChannelTest.java (1 hunks)
  • netty-socketio-core/src/test/java/com/socketio4j/socketio/integration/DistributedRedissonPubSubMultiChannelUnReliableTest.java (1 hunks)
  • netty-socketio-core/src/test/java/com/socketio4j/socketio/integration/DistributedRedissonPubSubSingleChannelUnreliableTest.java (1 hunks)
  • netty-socketio-core/src/test/java/com/socketio4j/socketio/integration/DistributedRedissonStreamMultiChannelTest.java (1 hunks)
  • netty-socketio-core/src/test/java/com/socketio4j/socketio/integration/DistributedRedissonStreamSingleChannelTest.java (1 hunks)
  • netty-socketio-core/src/test/java/com/socketio4j/socketio/store/HazelcastRingBufferStoreFactoryTest.java (3 hunks)
  • netty-socketio-core/src/test/java/com/socketio4j/socketio/store/RedissonStreamStoreFactoryTest.java (4 hunks)
  • netty-socketio-core/src/test/java/com/socketio4j/socketio/store/event/HazelcastRingBufferEventStoreTest.java (1 hunks)
  • netty-socketio-core/src/test/java/com/socketio4j/socketio/store/event/RedissonStreamEventStoreTest.java (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • netty-socketio-core/src/main/java/com/socketio4j/socketio/store/hazelcast_ringbuffer/HazelcastRingBufferEventStore.java
🧰 Additional context used
🧬 Code graph analysis (12)
netty-socketio-core/src/main/java/com/socketio4j/socketio/store/redis_stream/RedissonStreamStoreFactory.java (2)
netty-socketio-core/src/main/java/com/socketio4j/socketio/store/event/BaseStoreFactory.java (1)
  • BaseStoreFactory (37-245)
netty-socketio-core/src/main/java/com/socketio4j/socketio/store/redis_pubsub/RedissonStore.java (1)
  • RedissonStore (27-60)
netty-socketio-core/src/test/java/com/socketio4j/socketio/integration/DistributedRedissonStreamSingleChannelTest.java (2)
netty-socketio-core/src/main/java/com/socketio4j/socketio/store/redis_stream/RedissonStreamEventStore.java (1)
  • RedissonStreamEventStore (47-336)
netty-socketio-core/src/main/java/com/socketio4j/socketio/store/redis_stream/RedissonStreamStoreFactory.java (1)
  • RedissonStreamStoreFactory (36-80)
netty-socketio-core/src/test/java/com/socketio4j/socketio/integration/DistributedHazelcastPubSubSingleChannelUnreliableTest.java (3)
netty-socketio-core/src/test/java/com/socketio4j/socketio/store/CustomizedHazelcastContainer.java (1)
  • CustomizedHazelcastContainer (35-84)
netty-socketio-core/src/main/java/com/socketio4j/socketio/store/hazelcast/HazelcastEventStore.java (1)
  • HazelcastEventStore (41-214)
netty-socketio-core/src/main/java/com/socketio4j/socketio/store/hazelcast/HazelcastStoreFactory.java (1)
  • HazelcastStoreFactory (39-81)
netty-socketio-core/src/main/java/com/socketio4j/socketio/store/hazelcast_ringbuffer/HazelcastRingBufferStoreFactory.java (2)
netty-socketio-core/src/main/java/com/socketio4j/socketio/store/event/BaseStoreFactory.java (1)
  • BaseStoreFactory (37-245)
netty-socketio-core/src/main/java/com/socketio4j/socketio/store/hazelcast/HazelcastStore.java (1)
  • HazelcastStore (26-59)
netty-socketio-core/src/test/java/com/socketio4j/socketio/integration/DistributedRedissonStreamMultiChannelTest.java (2)
netty-socketio-core/src/main/java/com/socketio4j/socketio/store/redis_stream/RedissonStreamEventStore.java (1)
  • RedissonStreamEventStore (47-336)
netty-socketio-core/src/main/java/com/socketio4j/socketio/store/redis_stream/RedissonStreamStoreFactory.java (1)
  • RedissonStreamStoreFactory (36-80)
netty-socketio-core/src/test/java/com/socketio4j/socketio/store/RedissonStreamStoreFactoryTest.java (4)
netty-socketio-core/src/main/java/com/socketio4j/socketio/store/event/PublishConfig.java (1)
  • PublishConfig (28-71)
netty-socketio-core/src/main/java/com/socketio4j/socketio/store/redis_pubsub/RedissonEventStore.java (1)
  • RedissonEventStore (43-200)
netty-socketio-core/src/main/java/com/socketio4j/socketio/store/redis_pubsub/RedissonStore.java (1)
  • RedissonStore (27-60)
netty-socketio-core/src/main/java/com/socketio4j/socketio/store/redis_pubsub/RedissonStoreFactory.java (1)
  • RedissonStoreFactory (37-79)
netty-socketio-core/src/main/java/com/socketio4j/socketio/store/redis_stream/RedissonStreamEventStore.java (2)
netty-socketio-core/src/main/java/com/socketio4j/socketio/store/event/EventMessage.java (1)
  • EventMessage (21-44)
netty-socketio-core/src/main/java/com/socketio4j/socketio/store/redis_pubsub/RedissonEventStore.java (1)
  • Builder (144-198)
netty-socketio-core/src/main/java/com/socketio4j/socketio/store/hazelcast/HazelcastEventStore.java (2)
netty-socketio-core/src/main/java/com/socketio4j/socketio/store/event/EventMessage.java (1)
  • EventMessage (21-44)
netty-socketio-core/src/main/java/com/socketio4j/socketio/store/hazelcast_ringbuffer/HazelcastRingBufferEventStore.java (1)
  • Builder (162-222)
netty-socketio-core/src/main/java/com/socketio4j/socketio/store/hazelcast/HazelcastStoreFactory.java (2)
netty-socketio-core/src/main/java/com/socketio4j/socketio/store/event/BaseStoreFactory.java (1)
  • BaseStoreFactory (37-245)
netty-socketio-core/src/main/java/com/socketio4j/socketio/store/event/PublishConfig.java (1)
  • PublishConfig (28-71)
netty-socketio-core/src/test/java/com/socketio4j/socketio/store/HazelcastRingBufferStoreFactoryTest.java (3)
netty-socketio-core/src/main/java/com/socketio4j/socketio/store/hazelcast/HazelcastEventStore.java (1)
  • HazelcastEventStore (41-214)
netty-socketio-core/src/main/java/com/socketio4j/socketio/store/hazelcast_ringbuffer/HazelcastRingBufferEventStore.java (1)
  • HazelcastRingBufferEventStore (42-225)
netty-socketio-core/src/main/java/com/socketio4j/socketio/store/hazelcast_ringbuffer/HazelcastRingBufferStoreFactory.java (1)
  • HazelcastRingBufferStoreFactory (39-81)
netty-socketio-core/src/test/java/com/socketio4j/socketio/store/event/HazelcastRingBufferEventStoreTest.java (2)
netty-socketio-core/src/test/java/com/socketio4j/socketio/store/CustomizedHazelcastContainer.java (1)
  • CustomizedHazelcastContainer (35-84)
netty-socketio-core/src/main/java/com/socketio4j/socketio/store/hazelcast/HazelcastEventStore.java (1)
  • HazelcastEventStore (41-214)
netty-socketio-core/src/test/java/com/socketio4j/socketio/integration/DistributedHazelcastRingBufferMultiChannelTest.java (5)
netty-socketio-core/src/main/java/com/socketio4j/socketio/Configuration.java (1)
  • Configuration (32-193)
netty-socketio-core/src/test/java/com/socketio4j/socketio/store/CustomizedHazelcastContainer.java (1)
  • CustomizedHazelcastContainer (35-84)
netty-socketio-core/src/main/java/com/socketio4j/socketio/store/hazelcast/HazelcastEventStore.java (1)
  • HazelcastEventStore (41-214)
netty-socketio-core/src/main/java/com/socketio4j/socketio/store/hazelcast_ringbuffer/HazelcastRingBufferEventStore.java (1)
  • HazelcastRingBufferEventStore (42-225)
netty-socketio-core/src/main/java/com/socketio4j/socketio/store/hazelcast_ringbuffer/HazelcastRingBufferStoreFactory.java (1)
  • HazelcastRingBufferStoreFactory (39-81)
🪛 ast-grep (0.40.0)
netty-socketio-core/src/test/java/com/socketio4j/socketio/integration/DistributedHazelcastRingBufferSingleChannelTest.java

[info] 50-50: "Detected use of a Java socket that is not encrypted. As a result, the
traffic could be read by an attacker intercepting the network traffic. Use
an SSLSocket created by 'SSLSocketFactory' or 'SSLServerSocketFactory'
instead."
Context: new ServerSocket(0)
Note: [CWE-319] Cleartext Transmission of Sensitive Information [REFERENCES]
- https://owasp.org/Top10/A02_2021-Cryptographic_Failures

(unencrypted-socket-java)

netty-socketio-core/src/test/java/com/socketio4j/socketio/integration/DistributedRedissonPubSubMultiChannelUnReliableTest.java

[info] 46-46: "Detected use of a Java socket that is not encrypted. As a result, the
traffic could be read by an attacker intercepting the network traffic. Use
an SSLSocket created by 'SSLSocketFactory' or 'SSLServerSocketFactory'
instead."
Context: new ServerSocket(0)
Note: [CWE-319] Cleartext Transmission of Sensitive Information [REFERENCES]
- https://owasp.org/Top10/A02_2021-Cryptographic_Failures

(unencrypted-socket-java)

netty-socketio-core/src/test/java/com/socketio4j/socketio/integration/DistributedRedissonStreamSingleChannelTest.java

[info] 45-45: "Detected use of a Java socket that is not encrypted. As a result, the
traffic could be read by an attacker intercepting the network traffic. Use
an SSLSocket created by 'SSLSocketFactory' or 'SSLServerSocketFactory'
instead."
Context: new ServerSocket(0)
Note: [CWE-319] Cleartext Transmission of Sensitive Information [REFERENCES]
- https://owasp.org/Top10/A02_2021-Cryptographic_Failures

(unencrypted-socket-java)

netty-socketio-core/src/test/java/com/socketio4j/socketio/integration/DistributedHazelcastPubSubSingleChannelUnreliableTest.java

[info] 49-49: "Detected use of a Java socket that is not encrypted. As a result, the
traffic could be read by an attacker intercepting the network traffic. Use
an SSLSocket created by 'SSLSocketFactory' or 'SSLServerSocketFactory'
instead."
Context: new ServerSocket(0)
Note: [CWE-319] Cleartext Transmission of Sensitive Information [REFERENCES]
- https://owasp.org/Top10/A02_2021-Cryptographic_Failures

(unencrypted-socket-java)

netty-socketio-core/src/test/java/com/socketio4j/socketio/integration/DistributedRedissonStreamMultiChannelTest.java

[info] 46-46: "Detected use of a Java socket that is not encrypted. As a result, the
traffic could be read by an attacker intercepting the network traffic. Use
an SSLSocket created by 'SSLSocketFactory' or 'SSLServerSocketFactory'
instead."
Context: new ServerSocket(0)
Note: [CWE-319] Cleartext Transmission of Sensitive Information [REFERENCES]
- https://owasp.org/Top10/A02_2021-Cryptographic_Failures

(unencrypted-socket-java)

netty-socketio-core/src/test/java/com/socketio4j/socketio/integration/DistributedHazelcastPubSubMultiChannelUnReliableTest.java

[info] 45-45: "Detected use of a Java socket that is not encrypted. As a result, the
traffic could be read by an attacker intercepting the network traffic. Use
an SSLSocket created by 'SSLSocketFactory' or 'SSLServerSocketFactory'
instead."
Context: new ServerSocket(0)
Note: [CWE-319] Cleartext Transmission of Sensitive Information [REFERENCES]
- https://owasp.org/Top10/A02_2021-Cryptographic_Failures

(unencrypted-socket-java)

netty-socketio-core/src/test/java/com/socketio4j/socketio/integration/DistributedHazelcastRingBufferMultiChannelTest.java

[info] 51-51: "Detected use of a Java socket that is not encrypted. As a result, the
traffic could be read by an attacker intercepting the network traffic. Use
an SSLSocket created by 'SSLSocketFactory' or 'SSLServerSocketFactory'
instead."
Context: new ServerSocket(0)
Note: [CWE-319] Cleartext Transmission of Sensitive Information [REFERENCES]
- https://owasp.org/Top10/A02_2021-Cryptographic_Failures

(unencrypted-socket-java)

netty-socketio-core/src/test/java/com/socketio4j/socketio/integration/DistributedRedissonPubSubSingleChannelUnreliableTest.java

[info] 46-46: "Detected use of a Java socket that is not encrypted. As a result, the
traffic could be read by an attacker intercepting the network traffic. Use
an SSLSocket created by 'SSLSocketFactory' or 'SSLServerSocketFactory'
instead."
Context: new ServerSocket(0)
Note: [CWE-319] Cleartext Transmission of Sensitive Information [REFERENCES]
- https://owasp.org/Top10/A02_2021-Cryptographic_Failures

(unencrypted-socket-java)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (6)
  • GitHub Check: build (17) / build
  • GitHub Check: build (21) / build
  • GitHub Check: build (25) / build
  • GitHub Check: qodana
  • GitHub Check: Analyze (java-kotlin)
  • GitHub Check: Analyze (java-kotlin)
🔇 Additional comments (9)
netty-socketio-core/src/test/java/com/socketio4j/socketio/integration/DistributedRedissonPubSubSingleChannelUnreliableTest.java (1)

113-131: Teardown logic is well-structured.

Proper null checks and ordered shutdown of nodes → clients → container. This follows the correct inverse order of initialization.

netty-socketio-core/src/test/java/com/socketio4j/socketio/integration/DistributedRedissonStreamMultiChannelTest.java (1)

55-107: Setup structure looks correct for multi-channel stream test.

Both nodes are correctly configured with RedissonStreamStoreFactory and EventStoreMode.MULTI_CHANNEL. The event handlers for join-room/leave-room are appropriately registered.

Consider removing or replacing the commented Thread.sleep(600) with a deterministic readiness check for CI stability.

netty-socketio-core/src/test/java/com/socketio4j/socketio/integration/DistributedRedissonStreamSingleChannelTest.java (1)

54-104: Setup correctly configures SINGLE_CHANNEL mode for stream-based event store.

The test properly wires RedissonStreamStoreFactory with EventStoreMode.SINGLE_CHANNEL, matching the intended test scenario. Both nodes share the same Redis backend via separate clients.

netty-socketio-core/src/test/java/com/socketio4j/socketio/integration/DistributedHazelcastRingBufferSingleChannelTest.java (1)

59-115: Hazelcast ring buffer setup is correctly structured.

Both nodes are properly configured with HazelcastRingBufferStoreFactory and EventStoreMode.SINGLE_CHANNEL. The Hazelcast client configuration with setSmartRouting(false) is appropriate for containerized testing.

netty-socketio-core/src/main/java/com/socketio4j/socketio/store/hazelcast/HazelcastStoreFactory.java (1)

51-58: Constructor and factory implementation look good.

The constructor properly validates non-null arguments and stores the dependencies. The factory methods correctly delegate to the appropriate components, following the same pattern as HazelcastRingBufferStoreFactory.

netty-socketio-core/src/test/java/com/socketio4j/socketio/store/HazelcastRingBufferStoreFactoryTest.java (1)

59-76: Test setup correctly wires HazelcastRingBufferStoreFactory.

The factory is properly constructed with a HazelcastRingBufferEventStore built via its Builder pattern, consistent with the new EventStore architecture.

netty-socketio-core/src/test/java/com/socketio4j/socketio/integration/DistributedHazelcastRingBufferMultiChannelTest.java (2)

51-55: Safe usage for ephemeral port discovery.

The static analysis warning about unencrypted socket is a false positive here. Using ServerSocket(0) is a standard, safe technique for finding an available ephemeral port in tests—no sensitive data is transmitted. The socket is immediately closed after retrieving the port.


60-116: Distributed test setup looks correct.

The test properly initializes two independent Hazelcast-backed Socket.IO nodes with HazelcastRingBufferStoreFactory in MULTI_CHANNEL mode, allowing verification of cross-node communication via the event store.

netty-socketio-core/src/test/java/com/socketio4j/socketio/store/event/HazelcastRingBufferEventStoreTest.java (1)

36-55: Test setup and teardown structure is appropriate.

The test correctly manages the Hazelcast container lifecycle with reuse enabled and properly creates separate pub/sub client instances.

Comment on lines +96 to +101
private String getTopicName(EventType type) {
if (EventStoreMode.SINGLE_CHANNEL.equals(eventStoreMode)) {
return EventType.ALL_SINGLE_CHANNEL.name();
}
return type.name();
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

topicPrefix not applied in SINGLE_CHANNEL mode.

In getTopicName(), the topicPrefix is not prepended when SINGLE_CHANNEL mode is used, unlike the MULTI_CHANNEL path. This creates inconsistent topic naming and may cause topic collisions across different applications.

Apply this diff:

 private String getTopicName(EventType type) {
     if (EventStoreMode.SINGLE_CHANNEL.equals(eventStoreMode)) {
-        return EventType.ALL_SINGLE_CHANNEL.name();
+        return topicPrefix + EventType.ALL_SINGLE_CHANNEL.name();
     }
-    return type.name();
+    return topicPrefix + type.name();
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
private String getTopicName(EventType type) {
if (EventStoreMode.SINGLE_CHANNEL.equals(eventStoreMode)) {
return EventType.ALL_SINGLE_CHANNEL.name();
}
return type.name();
}
private String getTopicName(EventType type) {
if (EventStoreMode.SINGLE_CHANNEL.equals(eventStoreMode)) {
return topicPrefix + EventType.ALL_SINGLE_CHANNEL.name();
}
return topicPrefix + type.name();
}
🤖 Prompt for AI Agents
In
netty-socketio-core/src/main/java/com/socketio4j/socketio/store/hazelcast/HazelcastEventStore.java
around lines 96 to 101, the SINGLE_CHANNEL branch of getTopicName() returns
EventType.ALL_SINGLE_CHANNEL.name() without applying topicPrefix; update the
method so that when eventStoreMode is SINGLE_CHANNEL it returns topicPrefix +
EventType.ALL_SINGLE_CHANNEL.name() (matching the MULTI_CHANNEL path), taking
care to handle a null/empty topicPrefix consistently (e.g., treat null as empty
string) so topic naming remains consistent and avoids collisions.

Comment on lines 23 to 35
import org.jetbrains.annotations.NotNull;
import org.redisson.Redisson;
import org.redisson.api.RedissonClient;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.hazelcast.internal.services.ObjectNamespace;
import com.socketio4j.socketio.store.Store;
import com.socketio4j.socketio.store.event.BaseStoreFactory;
import com.socketio4j.socketio.store.event.EventStore;
import com.socketio4j.socketio.store.event.EventStoreMode;
import com.socketio4j.socketio.store.event.PublishConfig;

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Avoid depending on Hazelcast “internal” packages from the Redis module.
com.hazelcast.internal.services.ObjectNamespace is an internal API and shouldn’t be referenced here (even as an import), since it can break compilation across Hazelcast versions and signals unintended coupling.

-import com.hazelcast.internal.services.ObjectNamespace;
🤖 Prompt for AI Agents
In
netty-socketio-core/src/main/java/com/socketio4j/socketio/store/redis_pubsub/RedissonStoreFactory.java
around lines 23 to 35 the file imports the internal Hazelcast API
com.hazelcast.internal.services.ObjectNamespace which is an internal, unstable
dependency; remove that import and any usages of ObjectNamespace in this Redis
module and replace them with a project-owned type or a plain String/UUID-based
namespace representation (or use a public Hazelcast API if you truly need
Hazelcast types). Ensure all references are updated to the new type or string
constant, update any constructors/method signatures that referenced
ObjectNamespace, and run compilation/tests to verify no remaining references to
com.hazelcast.internal.* remain.

Comment on lines +98 to +100
if (streamMaxLength != Integer.MAX_VALUE) {
trimEvery = Duration.ofSeconds(60);
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

User-provided trimEvery is silently overwritten.

When streamMaxLength != Integer.MAX_VALUE, the user-provided trimEvery parameter is unconditionally overwritten with a 60-second default. This ignores the caller's explicit configuration and could lead to unexpected behavior.

Apply this diff to preserve user-provided values:

-        if (streamMaxLength != Integer.MAX_VALUE) {
-            trimEvery = Duration.ofSeconds(60);
-        }
+        if (streamMaxLength != Integer.MAX_VALUE && trimEvery == null) {
+            trimEvery = Duration.ofSeconds(60);
+            log.info("trimEvery defaulted to 60s because streamMaxLength is set");
+        }
🤖 Prompt for AI Agents
In
netty-socketio-core/src/main/java/com/socketio4j/socketio/store/redis_stream/RedissonStreamEventStore.java
around lines 98 to 100, the code unconditionally overwrites the user-provided
trimEvery when streamMaxLength != Integer.MAX_VALUE; change the logic to only
set the 60-second default when trimEvery is null (or not provided) so callers'
explicit values are preserved — i.e., check that trimEvery == null before
assigning Duration.ofSeconds(60); keep the existing behavior when trimEvery is
already set.

Comment on lines +26 to +31
import com.socketio4j.socketio.store.hazelcast.HazelcastEventStore;

/**
* Test class for HazelcastPubSubStore using testcontainers
*/
public class HazelcastPubSubStoreTest extends AbstractPubSubStoreTest {
public class HazelcastRingBufferEventStoreTest extends AbstractEventStoreTest {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Class name and implementation mismatch: uses HazelcastEventStore instead of HazelcastRingBufferEventStore.

The test class is named HazelcastRingBufferEventStoreTest but creates a HazelcastEventStore via HazelcastEventStore.Builder. Based on the class name and the relevant code snippets showing HazelcastRingBufferEventStore.Builder exists, this should use the ring buffer variant.

Apply this diff:

-import com.socketio4j.socketio.store.hazelcast.HazelcastEventStore;
+import com.socketio4j.socketio.store.hazelcast_ringbuffer.HazelcastRingBufferEventStore;
-        return new HazelcastEventStore.Builder(hazelcastPub, hazelcastSub).build();
+        return new HazelcastRingBufferEventStore.Builder(hazelcastPub, hazelcastSub).build();

Also applies to: 54-54

🤖 Prompt for AI Agents
In
netty-socketio-core/src/test/java/com/socketio4j/socketio/store/event/HazelcastRingBufferEventStoreTest.java
around lines 26-31 (and also at line 54), the test constructs a
HazelcastEventStore but the class name indicates it should test the ring-buffer
variant; change the import to use
com.socketio4j.socketio.store.hazelcast.HazelcastRingBufferEventStore, update
any instantiation/Builder calls to HazelcastRingBufferEventStore.Builder
(instead of HazelcastEventStore.Builder), and adjust any variable types and
imports accordingly so the test creates and asserts against the ring-buffer
implementation.

Comment on lines 25 to 30
import com.socketio4j.socketio.store.redis_pubsub.RedissonEventStore;

/**
* Test class for RedissonPubSubStore using testcontainers
* Test class for RedissonStreamEventStore using testcontainers
*/
public class RedissonPubSubStoreTest extends AbstractPubSubStoreTest {
public class RedissonStreamEventStoreTest extends AbstractEventStoreTest {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
echo "=== Searching for RedissonStreamEventStore class definition ==="
rg -t java "class.*RedissonStreamEventStore" -A 2

echo ""
echo "=== Searching for redis_stream directory ==="
fd -t d "redis_stream"

echo ""
echo "=== Listing redis package directories ==="
fd -t d "redis" | grep -E "redis_|redis/" | head -20

echo ""
echo "=== Checking EventStore implementations in redis-related packages ==="
rg -t java "implements.*EventStore" | grep -i redis

echo ""
echo "=== Examining the test file content ==="
cat -n netty-socketio-core/src/test/java/com/socketio4j/socketio/store/event/RedissonStreamEventStoreTest.java

Repository: socketio4j/netty-socketio

Length of output: 4765


Import and implementation mismatch: test imports pub/sub store instead of stream store.

The test class is named RedissonStreamEventStoreTest with documentation stating "Test class for RedissonStreamEventStore", but it imports and instantiates RedissonEventStore from the redis_pubsub package (line 25 and 49). The correct stream-based implementation RedissonStreamEventStore exists in the redis_stream package and should be used instead to match the class name and intent.

Comment on lines +114 to 119
public void testHazelcastEventStore() {
EventStore eventStore = storeFactory.eventStore();

assertNotNull(pubSubStore, "PubSubStore should not be null");
assertInstanceOf(HazelcastPubSubStore.class, pubSubStore, "PubSubStore should be HazelcastStore");
assertNotNull(eventStore, "EventStore should not be null");
assertInstanceOf(HazelcastEventStore.class, eventStore, "EventStore should be HazelcastStore");
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Incorrect assertion type: should check for HazelcastRingBufferEventStore.

The test creates a HazelcastRingBufferStoreFactory with a HazelcastRingBufferEventStore, but the assertion checks for HazelcastEventStore.class. These are different classes, so this assertion will fail.

Apply this diff:

 @Test
 public void testHazelcastEventStore() {
     EventStore eventStore = storeFactory.eventStore();
     
     assertNotNull(eventStore, "EventStore should not be null");
-    assertInstanceOf(HazelcastEventStore.class, eventStore, "EventStore should be HazelcastStore");
+    assertInstanceOf(HazelcastRingBufferEventStore.class, eventStore, "EventStore should be HazelcastRingBufferEventStore");
 }
🤖 Prompt for AI Agents
In
netty-socketio-core/src/test/java/com/socketio4j/socketio/store/HazelcastRingBufferStoreFactoryTest.java
around lines 114 to 119, the test asserts the wrong concrete class
(HazelcastEventStore) while the factory produces a
HazelcastRingBufferEventStore; update the assertion to
assertInstanceOf(HazelcastRingBufferEventStore.class, eventStore, "EventStore
should be HazelcastRingBufferEventStore") so the test checks for the correct
implementation and adjust the failure message accordingly.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (5)
netty-socketio-core/src/main/java/com/socketio4j/socketio/store/hazelcast_ringbuffer/HazelcastRingBufferStoreFactory.java (3)

39-39: Unused logger field.

The logger is declared but never used in this class. Consider removing it or adding relevant log statements (e.g., in the constructor or shutdown method for lifecycle tracking).


44-47: Clarify the API version comment.

The comment "API 4.y.z" is vague and doesn't provide meaningful information about the constructor's purpose or the API contract it represents.

Consider replacing it with a more descriptive comment:

-    /**
-     * API 4.y.z
-     * @param hazelcastClient
-     * @param eventStore
-     */
+    /**
+     * Creates a new Hazelcast ring-buffer store factory.
+     * 
+     * @param hazelcastClient the Hazelcast client instance for creating stores
+     * @param eventStore the ring-buffer-backed event store for pub/sub operations
+     */

49-77: Implementation looks solid, but note the code duplication.

The class correctly implements the BaseStoreFactory contract with proper null checks and delegation. However, this implementation is nearly identical to HazelcastStoreFactory.java (lines 48-76), differing only in the constructor parameter type (HazelcastRingBufferEventStore vs HazelcastEventStore). Consider extracting common logic into a shared base class or abstract factory method if more Hazelcast-based factories are planned.

netty-socketio-core/src/main/java/com/socketio4j/socketio/store/hazelcast/HazelcastStoreFactory.java (2)

38-38: Unused logger field.

The logger is declared but never used in this class. Consider removing it or adding relevant log statements (e.g., in the constructor or shutdown method for lifecycle tracking).


43-47: Clarify the API version comment.

The comment "API 4.y.z" is vague and doesn't provide meaningful information about the constructor's purpose or the API contract it represents.

Consider replacing it with a more descriptive comment:

-    /**
-     * API 4.y.z
-     * @param hazelcastClient
-     * @param eventStore
-     */
+    /**
+     * Creates a new Hazelcast store factory.
+     * 
+     * @param hazelcastClient the Hazelcast client instance for creating stores
+     * @param eventStore the Hazelcast-backed event store for pub/sub operations
+     */
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 8977420 and 83a07b5.

📒 Files selected for processing (4)
  • netty-socketio-core/src/main/java/com/socketio4j/socketio/store/hazelcast/HazelcastStoreFactory.java (1 hunks)
  • netty-socketio-core/src/main/java/com/socketio4j/socketio/store/hazelcast_ringbuffer/HazelcastRingBufferStoreFactory.java (1 hunks)
  • netty-socketio-core/src/main/java/com/socketio4j/socketio/store/redis_pubsub/RedissonStoreFactory.java (1 hunks)
  • netty-socketio-core/src/main/java/com/socketio4j/socketio/store/redis_stream/RedissonStreamStoreFactory.java (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • netty-socketio-core/src/main/java/com/socketio4j/socketio/store/redis_stream/RedissonStreamStoreFactory.java
  • netty-socketio-core/src/main/java/com/socketio4j/socketio/store/redis_pubsub/RedissonStoreFactory.java
🧰 Additional context used
🧬 Code graph analysis (1)
netty-socketio-core/src/main/java/com/socketio4j/socketio/store/hazelcast_ringbuffer/HazelcastRingBufferStoreFactory.java (2)
netty-socketio-core/src/main/java/com/socketio4j/socketio/store/event/BaseStoreFactory.java (1)
  • BaseStoreFactory (37-245)
netty-socketio-core/src/main/java/com/socketio4j/socketio/store/hazelcast/HazelcastStore.java (1)
  • HazelcastStore (26-59)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: build (17) / build
  • GitHub Check: build (25) / build
  • GitHub Check: build (21) / build
🔇 Additional comments (1)
netty-socketio-core/src/main/java/com/socketio4j/socketio/store/hazelcast/HazelcastStoreFactory.java (1)

48-76: Implementation looks solid.

The class correctly implements the BaseStoreFactory contract with proper null checks and delegation. The code duplication with HazelcastRingBufferStoreFactory.java has been noted in that file's review.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

♻️ Duplicate comments (1)
netty-socketio-core/src/test/java/com/socketio4j/socketio/store/event/HazelcastRingBufferEventStoreTest.java (1)

26-26: Class name and implementation mismatch: uses HazelcastEventStore instead of HazelcastRingBufferEventStore.

The test class is named HazelcastRingBufferEventStoreTest but creates a HazelcastEventStore via HazelcastEventStore.Builder. Based on the class name and the relevant code snippets showing HazelcastRingBufferEventStore is used in other tests (e.g., DistributedHazelcastRingBufferMultiChannelTest), this should use the ring buffer variant.

Apply this diff:

-import com.socketio4j.socketio.store.hazelcast.HazelcastEventStore;
+import com.socketio4j.socketio.store.hazelcast_ringbuffer.HazelcastRingBufferEventStore;
-        return new HazelcastEventStore.Builder(hazelcastPub, hazelcastSub).nodeId(nodeId).build();
+        return new HazelcastRingBufferEventStore.Builder(hazelcastPub, hazelcastSub).nodeId(nodeId).build();

Also applies to: 54-54

🧹 Nitpick comments (2)
netty-socketio-core/src/test/java/com/socketio4j/socketio/store/event/HazelcastRingBufferEventStoreTest.java (2)

19-19: Remove unused import.

The TestInstance annotation is imported but not applied to the test class.

Apply this diff:

-import org.junit.jupiter.api.TestInstance;

29-29: Update outdated comment.

The comment references HazelcastPubSubStore but should reference HazelcastRingBufferEventStore to match the test class name.

Apply this diff:

-/**
- * Test class for HazelcastPubSubStore using testcontainers
- */
+/**
+ * Test class for HazelcastRingBufferEventStore using testcontainers
+ */
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 83a07b5 and 50b48a6.

📒 Files selected for processing (1)
  • netty-socketio-core/src/test/java/com/socketio4j/socketio/store/event/HazelcastRingBufferEventStoreTest.java (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
netty-socketio-core/src/test/java/com/socketio4j/socketio/store/event/HazelcastRingBufferEventStoreTest.java (4)
netty-socketio-core/src/test/java/com/socketio4j/socketio/integration/DistributedHazelcastPubSubSingleChannelUnreliableTest.java (1)
  • TestInstance (40-140)
netty-socketio-core/src/test/java/com/socketio4j/socketio/integration/DistributedHazelcastRingBufferMultiChannelTest.java (1)
  • TestInstance (42-139)
netty-socketio-core/src/test/java/com/socketio4j/socketio/store/CustomizedHazelcastContainer.java (1)
  • CustomizedHazelcastContainer (35-84)
netty-socketio-core/src/main/java/com/socketio4j/socketio/store/hazelcast/HazelcastEventStore.java (1)
  • HazelcastEventStore (41-214)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (6)
  • GitHub Check: build (21) / build
  • GitHub Check: build (17) / build
  • GitHub Check: build (25) / build
  • GitHub Check: qodana
  • GitHub Check: Analyze (java-kotlin)
  • GitHub Check: Analyze (java-kotlin)

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
netty-socketio-core/src/test/java/com/socketio4j/socketio/store/event/RedissonReliableEventStoreTest.java (1)

25-49: Test class name does not match the implementation under test.

The class is named RedissonReliableEventStoreTest, but it instantiates RedissonEventStore (the non-reliable, pub/sub-based implementation) rather than RedissonReliableEventStore (which uses RReliableTopic).

Either rename the test class to RedissonEventStoreTest, or update the import and instantiation to use the reliable variant:

-import com.socketio4j.socketio.store.redis_pubsub.RedissonEventStore;
+import com.socketio4j.socketio.store.redis_reliable.RedissonReliableEventStore;
-        return new RedissonEventStore(redissonPub, redissonSub, EventStoreMode.MULTI_CHANNEL, nodeId);
+        return new RedissonReliableEventStore.Builder(redissonPub, redissonSub)
+                .eventStoreMode(EventStoreMode.MULTI_CHANNEL)
+                .nodeId(nodeId)
+                .build();
netty-socketio-core/src/test/java/com/socketio4j/socketio/store/RedissonReliableStoreFactoryTest.java (1)

46-68: Test class name mismatch with factory under test.

The test class is named RedissonReliableStoreFactoryTest but it instantiates RedissonStoreFactory (lines 66-67), not RedissonReliableStoreFactory.

Either rename the test class to match the factory being tested, or update to test the reliable variant:

-        return new RedissonStoreFactory(redissonClient,
-                new RedissonEventStore.Builder(redissonClient).build());
+        return new RedissonReliableStoreFactory(redissonClient,
+                new RedissonReliableEventStore.Builder(redissonClient).build());

This would also require updating the imports accordingly.

♻️ Duplicate comments (3)
netty-socketio-core/src/main/java/com/socketio4j/socketio/store/redis_reliable/RedissonReliableStoreFactory.java (1)

66-69: Missing shutdown of Redisson client - potential resource leak.

The shutdown() method only delegates to eventStore.shutdown() but does not shut down the redissonClient. This could lead to resource leaks if the client is not externally managed. This was flagged in a previous review.

netty-socketio-core/src/main/java/com/socketio4j/socketio/store/redis_reliable/RedissonReliableEventStore.java (2)

98-100: User-provided trimEvery is silently overwritten.

When streamMaxLength != Integer.MAX_VALUE, the user-provided trimEvery parameter is unconditionally overwritten with a 60-second default. This ignores the caller's explicit configuration.

-        if (streamMaxLength != Integer.MAX_VALUE) {
-            trimEvery = Duration.ofSeconds(60);
-        }
+        if (streamMaxLength != Integer.MAX_VALUE && trimEvery == null) {
+            trimEvery = Duration.ofSeconds(60);
+            log.info("trimEvery defaulted to 60s because streamMaxLength is set");
+        }

243-251: Redundant shutdown() call before shutdownNow().

Line 244 calls trimExecutor.shutdown() and line 247 immediately calls trimExecutor.shutdownNow() without waiting. The shutdown() call is redundant because shutdownNow() already attempts to stop all actively executing tasks.

🧹 Nitpick comments (10)
netty-socketio-core/src/test/java/com/socketio4j/socketio/integration/DistributedHazelcastRingBufferSingleChannelTest.java (1)

109-109: Remove commented debugging code.

This commented Thread.sleep(600) appears to be a debugging artifact and should be removed to keep the codebase clean.

Apply this diff:

-        //Thread.sleep(600);
netty-socketio-core/src/test/java/com/socketio4j/socketio/integration/DistributedHazelcastRingBufferMultiChannelTest.java (1)

109-109: Remove commented debugging code.

This commented Thread.sleep(600) appears to be a debugging artifact and should be removed to keep the codebase clean.

Apply this diff:

-        //Thread.sleep(600);
netty-socketio-core/src/test/java/com/socketio4j/socketio/integration/DistributedRedissonStreamMultiChannelTest.java (2)

105-105: Remove commented debugging code.

The commented Thread.sleep(600) should be removed if it's no longer needed for debugging.

Apply this diff:

-        //Thread.sleep(600);

62-103: Consider extracting common node setup logic.

Both node configurations follow an identical pattern (configure, create handlers, start). Extracting this to a helper method would reduce duplication and make the test more maintainable.

Example refactor:

private SocketIOServer createNode(RedissonClient client, EventStoreMode mode) throws Exception {
    Configuration cfg = new Configuration();
    cfg.setHostname("127.0.0.1");
    cfg.setPort(findAvailablePort());
    cfg.setStoreFactory(new RedissonReliableStoreFactory(
        client,
        new RedissonReliableEventStore.Builder(client).eventStoreMode(mode).build()
    ));
    
    SocketIOServer node = new SocketIOServer(cfg);
    node.addEventListener("join-room", String.class, (c, room, ack) -> {
        c.joinRoom(room);
        c.sendEvent("join-ok", "OK");
    });
    node.addEventListener("leave-room", String.class, (c, room, ack) -> {
        c.leaveRoom(room);
        c.sendEvent("leave-ok", "OK");
    });
    node.start();
    return node;
}
netty-socketio-core/src/test/java/com/socketio4j/socketio/integration/DistributedRedissonStreamSingleChannelTest.java (2)

103-103: Remove commented debugging code.

The commented Thread.sleep(600) should be removed if it's no longer needed.

Apply this diff:

-        //Thread.sleep(600);

62-101: Consider extracting common node setup logic.

Like the MultiChannel test, both nodes have identical setup patterns. Consider extracting to a helper method to reduce duplication.

Additionally, since both DistributedRedissonStreamMultiChannelTest and DistributedRedissonStreamSingleChannelTest share nearly identical code except for the EventStoreMode, consider creating a shared base class or parameterized test to eliminate cross-file duplication.

netty-socketio-core/src/main/java/module-info.java (1)

13-37: Inconsistent indentation in module descriptor.

Lines 16-20 and 37 use 4-space indentation while the rest of the file uses 2-space indentation. Consider normalizing for consistency:

   exports com.socketio4j.socketio.store.event;
   exports com.socketio4j.socketio.transport;
   exports com.socketio4j.socketio.nativeio;
-    exports com.socketio4j.socketio.store.hazelcast;
-    exports com.socketio4j.socketio.store.memory;
-    exports com.socketio4j.socketio.store.redis_pubsub;
-    exports com.socketio4j.socketio.store.redis_reliable;
-    requires com.fasterxml.jackson.core;
+  exports com.socketio4j.socketio.store.hazelcast;
+  exports com.socketio4j.socketio.store.memory;
+  exports com.socketio4j.socketio.store.redis_pubsub;
+  exports com.socketio4j.socketio.store.redis_reliable;
+  requires com.fasterxml.jackson.core;
   requires com.fasterxml.jackson.annotation;
...
   requires org.slf4j;
-    requires org.jetbrains.annotations;
+  requires org.jetbrains.annotations;
netty-socketio-core/src/main/java/com/socketio4j/socketio/store/redis_reliable/RedissonReliableStoreFactory.java (1)

35-35: Unused logger field.

The log field is declared but never used in this class. Either remove it or add appropriate logging statements.

-    private static final Logger log = LoggerFactory.getLogger(RedissonReliableStoreFactory.class);
netty-socketio-core/src/main/java/com/socketio4j/socketio/store/redis_reliable/RedissonReliableEventStore.java (2)

109-123: Executor is created unconditionally but only used when trimEvery != null.

The ScheduledExecutorService is always instantiated (lines 109-114), but trimming is only scheduled when trimEvery != null. If trimEvery is null, the executor is created but never used, resulting in an idle daemon thread until shutdown.

Consider deferring executor creation:

-        ScheduledExecutorService executorService = Executors.newSingleThreadScheduledExecutor(r -> {
-            Thread t = new Thread(r, "socketio4j-redis-stream-trimmer");
-            t.setDaemon(true);
-            return t;
-        });
-        this.trimExecutor = executorService;
         this.trimEvery = trimEvery;
         if (trimEvery != null) {
+            ScheduledExecutorService executorService = Executors.newSingleThreadScheduledExecutor(r -> {
+                Thread t = new Thread(r, "socketio4j-redis-stream-trimmer");
+                t.setDaemon(true);
+                return t;
+            });
+            this.trimExecutor = executorService;
             executorService.scheduleAtFixedRate(
                     this::trimAllReliableStreams,
                     Duration.ofSeconds(1).getSeconds(),
                     trimEvery.getSeconds(),
                     TimeUnit.SECONDS
             );
+        } else {
+            this.trimExecutor = null;
         }

This would also require a null check in shutdown0().


201-202: Remove redundant null check on getReliableTopic() result.

redissonSub.getReliableTopic() returns a client-side proxy object and never returns null—it creates the topic if it doesn't exist. The Objects.requireNonNull check is redundant.

         RReliableTopic reliableTopic = redissonSub.getReliableTopic(getStreamName(type));
-        Objects.requireNonNull(reliableTopic, "reliableTopic can not be null");
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 50b48a6 and 3a90b61.

📒 Files selected for processing (9)
  • netty-socketio-core/src/main/java/com/socketio4j/socketio/store/redis_reliable/RedissonReliableEventStore.java (1 hunks)
  • netty-socketio-core/src/main/java/com/socketio4j/socketio/store/redis_reliable/RedissonReliableStoreFactory.java (1 hunks)
  • netty-socketio-core/src/main/java/module-info.java (2 hunks)
  • netty-socketio-core/src/test/java/com/socketio4j/socketio/integration/DistributedHazelcastRingBufferMultiChannelTest.java (1 hunks)
  • netty-socketio-core/src/test/java/com/socketio4j/socketio/integration/DistributedHazelcastRingBufferSingleChannelTest.java (1 hunks)
  • netty-socketio-core/src/test/java/com/socketio4j/socketio/integration/DistributedRedissonStreamMultiChannelTest.java (1 hunks)
  • netty-socketio-core/src/test/java/com/socketio4j/socketio/integration/DistributedRedissonStreamSingleChannelTest.java (1 hunks)
  • netty-socketio-core/src/test/java/com/socketio4j/socketio/store/RedissonReliableStoreFactoryTest.java (4 hunks)
  • netty-socketio-core/src/test/java/com/socketio4j/socketio/store/event/RedissonReliableEventStoreTest.java (2 hunks)
🧰 Additional context used
🧬 Code graph analysis (5)
netty-socketio-core/src/main/java/com/socketio4j/socketio/store/redis_reliable/RedissonReliableStoreFactory.java (2)
netty-socketio-core/src/main/java/com/socketio4j/socketio/store/event/BaseStoreFactory.java (1)
  • BaseStoreFactory (37-245)
netty-socketio-core/src/main/java/com/socketio4j/socketio/store/redis_pubsub/RedissonStore.java (1)
  • RedissonStore (27-60)
netty-socketio-core/src/main/java/com/socketio4j/socketio/store/redis_reliable/RedissonReliableEventStore.java (1)
netty-socketio-core/src/main/java/com/socketio4j/socketio/store/event/EventMessage.java (1)
  • EventMessage (21-44)
netty-socketio-core/src/test/java/com/socketio4j/socketio/store/event/RedissonReliableEventStoreTest.java (1)
netty-socketio-core/src/main/java/com/socketio4j/socketio/store/redis_pubsub/RedissonEventStore.java (1)
  • RedissonEventStore (43-200)
netty-socketio-core/src/test/java/com/socketio4j/socketio/integration/DistributedRedissonStreamMultiChannelTest.java (3)
netty-socketio-core/src/main/java/com/socketio4j/socketio/Configuration.java (1)
  • Configuration (32-193)
netty-socketio-core/src/main/java/com/socketio4j/socketio/store/redis_reliable/RedissonReliableEventStore.java (1)
  • RedissonReliableEventStore (47-336)
netty-socketio-core/src/main/java/com/socketio4j/socketio/store/redis_reliable/RedissonReliableStoreFactory.java (1)
  • RedissonReliableStoreFactory (33-77)
netty-socketio-core/src/test/java/com/socketio4j/socketio/integration/DistributedRedissonStreamSingleChannelTest.java (3)
netty-socketio-core/src/main/java/com/socketio4j/socketio/Configuration.java (1)
  • Configuration (32-193)
netty-socketio-core/src/main/java/com/socketio4j/socketio/store/redis_reliable/RedissonReliableEventStore.java (1)
  • RedissonReliableEventStore (47-336)
netty-socketio-core/src/main/java/com/socketio4j/socketio/store/redis_reliable/RedissonReliableStoreFactory.java (1)
  • RedissonReliableStoreFactory (33-77)
🪛 ast-grep (0.40.0)
netty-socketio-core/src/test/java/com/socketio4j/socketio/integration/DistributedHazelcastRingBufferSingleChannelTest.java

[info] 45-45: "Detected use of a Java socket that is not encrypted. As a result, the
traffic could be read by an attacker intercepting the network traffic. Use
an SSLSocket created by 'SSLSocketFactory' or 'SSLServerSocketFactory'
instead."
Context: new ServerSocket(0)
Note: [CWE-319] Cleartext Transmission of Sensitive Information [REFERENCES]
- https://owasp.org/Top10/A02_2021-Cryptographic_Failures

(unencrypted-socket-java)

netty-socketio-core/src/test/java/com/socketio4j/socketio/integration/DistributedRedissonStreamMultiChannelTest.java

[info] 45-45: "Detected use of a Java socket that is not encrypted. As a result, the
traffic could be read by an attacker intercepting the network traffic. Use
an SSLSocket created by 'SSLSocketFactory' or 'SSLServerSocketFactory'
instead."
Context: new ServerSocket(0)
Note: [CWE-319] Cleartext Transmission of Sensitive Information [REFERENCES]
- https://owasp.org/Top10/A02_2021-Cryptographic_Failures

(unencrypted-socket-java)

netty-socketio-core/src/test/java/com/socketio4j/socketio/integration/DistributedRedissonStreamSingleChannelTest.java

[info] 45-45: "Detected use of a Java socket that is not encrypted. As a result, the
traffic could be read by an attacker intercepting the network traffic. Use
an SSLSocket created by 'SSLSocketFactory' or 'SSLServerSocketFactory'
instead."
Context: new ServerSocket(0)
Note: [CWE-319] Cleartext Transmission of Sensitive Information [REFERENCES]
- https://owasp.org/Top10/A02_2021-Cryptographic_Failures

(unencrypted-socket-java)

netty-socketio-core/src/test/java/com/socketio4j/socketio/integration/DistributedHazelcastRingBufferMultiChannelTest.java

[info] 45-45: "Detected use of a Java socket that is not encrypted. As a result, the
traffic could be read by an attacker intercepting the network traffic. Use
an SSLSocket created by 'SSLSocketFactory' or 'SSLServerSocketFactory'
instead."
Context: new ServerSocket(0)
Note: [CWE-319] Cleartext Transmission of Sensitive Information [REFERENCES]
- https://owasp.org/Top10/A02_2021-Cryptographic_Failures

(unencrypted-socket-java)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (6)
  • GitHub Check: build (17) / build
  • GitHub Check: build (21) / build
  • GitHub Check: build (25) / build
  • GitHub Check: qodana
  • GitHub Check: Analyze (java-kotlin)
  • GitHub Check: Analyze (java-kotlin)
🔇 Additional comments (8)
netty-socketio-core/src/test/java/com/socketio4j/socketio/integration/DistributedRedissonStreamMultiChannelTest.java (4)

36-42: LGTM! Well-structured test class setup.

The use of PER_CLASS lifecycle with container reuse is appropriate for integration tests with expensive external dependencies.


45-49: Static analysis warning is a false positive.

The ServerSocket(0) is used solely to obtain an available port number for test server configuration. No network traffic passes through this socket, so the unencrypted socket warning does not apply. The try-with-resources pattern ensures proper cleanup.


108-112: LGTM! Clean configuration helper.

Simple and focused helper method for creating Redisson configuration.


114-132: LGTM! Proper resource cleanup.

The teardown follows the correct order (nodes → clients → container) and includes appropriate null checks for safety.

netty-socketio-core/src/test/java/com/socketio4j/socketio/integration/DistributedRedissonStreamSingleChannelTest.java (4)

36-42: LGTM! Consistent test structure.

The test setup mirrors the MultiChannel variant appropriately. Both tests follow the same pattern, which is good for consistency.


45-49: Static analysis warning is a false positive.

Same as the MultiChannel test—ServerSocket(0) is used only for port discovery, not data transmission.


106-110: LGTM! Clean configuration helper.


112-130: LGTM! Proper resource cleanup.

The teardown correctly cleans up resources in the proper order with appropriate null checks.

}

// -------------------------------------------
// Redis + Node Setup
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Fix the copy-paste comment referencing Redis.

This comment references "Redis + Node Setup" but this is a Hazelcast test.

Apply this diff:

-    // Redis + Node Setup
+    // Hazelcast + Node Setup
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// Redis + Node Setup
// Hazelcast + Node Setup
🤖 Prompt for AI Agents
In
netty-socketio-core/src/test/java/com/socketio4j/socketio/integration/DistributedHazelcastRingBufferMultiChannelTest.java
around line 52, the inline comment erroneously reads "Redis + Node Setup";
update that comment to correctly reference Hazelcast (e.g., "Hazelcast + Node
Setup" or simply "Hazelcast Setup") so the comment matches the test's purpose
and remove the misleading Redis reference.

}

// -------------------------------------------
// Redis + Node Setup
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Fix the copy-paste comment referencing Redis.

This comment references "Redis + Node Setup" but this is a Hazelcast test.

Apply this diff:

-    // Redis + Node Setup
+    // Hazelcast + Node Setup
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// Redis + Node Setup
// Hazelcast + Node Setup
🤖 Prompt for AI Agents
In
netty-socketio-core/src/test/java/com/socketio4j/socketio/integration/DistributedHazelcastRingBufferSingleChannelTest.java
around line 52, the inline comment incorrectly reads "Redis + Node Setup" due to
copy-paste; update it to accurately describe the test setup (e.g., "Hazelcast
setup" or "Hazelcast + Node Setup") so the comment reflects Hazelcast usage in
this test. Ensure the replacement matches surrounding comment style and
terminology used in other Hazelcast tests.

Comment on lines +109 to +110
assertNotNull(eventStore, "PubSubStore should not be null");
assertTrue(eventStore instanceof RedissonEventStore, "PubSubStore should be RedissonPubSubStore");
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Outdated assertion messages reference old naming.

The assertion messages still reference the old "PubSubStore" naming while the code has migrated to "EventStore".

-        assertNotNull(eventStore, "PubSubStore should not be null");
-        assertTrue(eventStore instanceof RedissonEventStore, "PubSubStore should be RedissonPubSubStore");
+        assertNotNull(eventStore, "EventStore should not be null");
+        assertTrue(eventStore instanceof RedissonEventStore, "EventStore should be RedissonEventStore");
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
assertNotNull(eventStore, "PubSubStore should not be null");
assertTrue(eventStore instanceof RedissonEventStore, "PubSubStore should be RedissonPubSubStore");
assertNotNull(eventStore, "EventStore should not be null");
assertTrue(eventStore instanceof RedissonEventStore, "EventStore should be RedissonEventStore");
🤖 Prompt for AI Agents
In
netty-socketio-core/src/test/java/com/socketio4j/socketio/store/RedissonReliableStoreFactoryTest.java
around lines 109 to 110, the test assertion failure messages still reference the
old "PubSubStore" name; update both messages to use the current "EventStore"
terminology (e.g., change "PubSubStore should not be null" to "EventStore should
not be null" and "PubSubStore should be RedissonPubSubStore" to "EventStore
should be RedissonEventStore") so the messages match the migrated class names.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

♻️ Duplicate comments (2)
netty-socketio-core/src/test/java/com/socketio4j/socketio/integration/DistributedHazelcastRingBufferSingleChannelTest.java (1)

52-52: Fix the copy-paste comment referencing Redis.

This comment incorrectly references "Redis + Node Setup" but this is a Hazelcast test.

Apply this diff:

-    // Redis + Node Setup
+    // Hazelcast + Node Setup
netty-socketio-core/src/test/java/com/socketio4j/socketio/integration/DistributedHazelcastRingBufferMultiChannelTest.java (1)

52-52: Fix the copy-paste comment referencing Redis.

This comment incorrectly references "Redis + Node Setup" but this is a Hazelcast test.

Apply this diff:

-    // Redis + Node Setup
+    // Hazelcast + Node Setup
🧹 Nitpick comments (5)
netty-socketio-core/src/test/java/com/socketio4j/socketio/integration/DistributedRedissonStreamSingleChannelTest.java (1)

104-104: Remove or document the commented sleep statement.

The commented Thread.sleep(600) suggests there may have been timing issues during development. Either remove this line if it's no longer needed, or if there's a legitimate race condition, uncomment it and add a comment explaining why the delay is necessary for test reliability.

netty-socketio-core/src/test/java/com/socketio4j/socketio/integration/DistributedHazelcastRingBufferSingleChannelTest.java (1)

109-109: Remove or document the commented-out sleep statement.

The commented-out Thread.sleep(600) should either be removed if no longer needed, or retained with a comment explaining why it's kept for potential future debugging.

Apply this diff to remove it:

-        //Thread.sleep(600);
netty-socketio-core/src/test/java/com/socketio4j/socketio/integration/DistributedHazelcastRingBufferMultiChannelTest.java (2)

109-109: Remove or document the commented-out sleep statement.

The commented-out Thread.sleep(600) should either be removed if no longer needed, or retained with a comment explaining why it's kept for potential future debugging.

Apply this diff to remove it:

-        //Thread.sleep(600);

36-131: Consider consolidating duplicated code between Single and Multi channel tests.

This test class is nearly identical to DistributedHazelcastRingBufferSingleChannelTest.java, differing only in the EventStoreMode parameter. Consider using a parameterized test approach or extracting common setup logic to a shared method to reduce duplication and improve maintainability.

For example, you could create a parameterized test:

@ParameterizedTest
@EnumSource(EventStoreMode.class)
public void testDistributedHazelcastRingBuffer(EventStoreMode mode) throws Exception {
    // Common setup with mode parameter
}

Or extract a common setup helper in the base class DistributedCommonTest.

netty-socketio-core/src/test/java/com/socketio4j/socketio/integration/DistributedRedissonStreamMultiChannelTest.java (1)

72-79: Consider extracting duplicated event handler registration.

Both nodes register identical join-room and leave-room event handlers. Extracting this logic into a helper method would reduce duplication and improve maintainability.

Example refactor:

private void registerRoomHandlers(SocketIOServer server) {
    server.addEventListener("join-room", String.class, (c, room, ack) -> {
        c.joinRoom(room);
        c.sendEvent("join-ok", "OK");
    });
    server.addEventListener("leave-room", String.class, (c, room, ack) -> {
        c.leaveRoom(room);
        c.sendEvent("leave-ok", "OK");
    });
}

Then call registerRoomHandlers(node1) and registerRoomHandlers(node2) in the setup method.

Also applies to: 94-101

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 3a90b61 and 4675dae.

📒 Files selected for processing (6)
  • netty-socketio-core/src/main/java/com/socketio4j/socketio/store/hazelcast/HazelcastStoreFactory.java (1 hunks)
  • netty-socketio-core/src/test/java/com/socketio4j/socketio/integration/DistributedHazelcastRingBufferMultiChannelTest.java (1 hunks)
  • netty-socketio-core/src/test/java/com/socketio4j/socketio/integration/DistributedHazelcastRingBufferSingleChannelTest.java (1 hunks)
  • netty-socketio-core/src/test/java/com/socketio4j/socketio/integration/DistributedRedissonStreamMultiChannelTest.java (1 hunks)
  • netty-socketio-core/src/test/java/com/socketio4j/socketio/integration/DistributedRedissonStreamSingleChannelTest.java (1 hunks)
  • netty-socketio-core/src/test/java/com/socketio4j/socketio/store/HazelcastStoreFactoryTest.java (3 hunks)
🧰 Additional context used
🧬 Code graph analysis (4)
netty-socketio-core/src/test/java/com/socketio4j/socketio/integration/DistributedHazelcastRingBufferMultiChannelTest.java (3)
netty-socketio-core/src/test/java/com/socketio4j/socketio/integration/DistributedHazelcastRingBufferSingleChannelTest.java (1)
  • TestInstance (36-132)
netty-socketio-core/src/test/java/com/socketio4j/socketio/store/CustomizedHazelcastContainer.java (1)
  • CustomizedHazelcastContainer (35-84)
netty-socketio-core/src/main/java/com/socketio4j/socketio/store/hazelcast/HazelcastStoreFactory.java (1)
  • HazelcastStoreFactory (36-78)
netty-socketio-core/src/test/java/com/socketio4j/socketio/integration/DistributedHazelcastRingBufferSingleChannelTest.java (3)
netty-socketio-core/src/test/java/com/socketio4j/socketio/store/CustomizedHazelcastContainer.java (1)
  • CustomizedHazelcastContainer (35-84)
netty-socketio-core/src/main/java/com/socketio4j/socketio/store/hazelcast/HazelcastStoreFactory.java (1)
  • HazelcastStoreFactory (36-78)
netty-socketio-core/src/main/java/com/socketio4j/socketio/store/hazelcast_ringbuffer/HazelcastRingBufferEventStore.java (1)
  • HazelcastRingBufferEventStore (42-225)
netty-socketio-core/src/main/java/com/socketio4j/socketio/store/hazelcast/HazelcastStoreFactory.java (2)
netty-socketio-core/src/main/java/com/socketio4j/socketio/store/event/BaseStoreFactory.java (1)
  • BaseStoreFactory (37-245)
netty-socketio-core/src/main/java/com/socketio4j/socketio/store/HazelcastStoreFactory.java (1)
  • HazelcastStoreFactory (38-130)
netty-socketio-core/src/test/java/com/socketio4j/socketio/integration/DistributedRedissonStreamMultiChannelTest.java (2)
netty-socketio-core/src/main/java/com/socketio4j/socketio/store/redis_pubsub/RedissonStoreFactory.java (1)
  • RedissonStoreFactory (33-75)
netty-socketio-core/src/main/java/com/socketio4j/socketio/store/redis_reliable/RedissonReliableEventStore.java (1)
  • RedissonReliableEventStore (47-336)
🪛 ast-grep (0.40.0)
netty-socketio-core/src/test/java/com/socketio4j/socketio/integration/DistributedHazelcastRingBufferMultiChannelTest.java

[info] 45-45: "Detected use of a Java socket that is not encrypted. As a result, the
traffic could be read by an attacker intercepting the network traffic. Use
an SSLSocket created by 'SSLSocketFactory' or 'SSLServerSocketFactory'
instead."
Context: new ServerSocket(0)
Note: [CWE-319] Cleartext Transmission of Sensitive Information [REFERENCES]
- https://owasp.org/Top10/A02_2021-Cryptographic_Failures

(unencrypted-socket-java)

netty-socketio-core/src/test/java/com/socketio4j/socketio/integration/DistributedHazelcastRingBufferSingleChannelTest.java

[info] 45-45: "Detected use of a Java socket that is not encrypted. As a result, the
traffic could be read by an attacker intercepting the network traffic. Use
an SSLSocket created by 'SSLSocketFactory' or 'SSLServerSocketFactory'
instead."
Context: new ServerSocket(0)
Note: [CWE-319] Cleartext Transmission of Sensitive Information [REFERENCES]
- https://owasp.org/Top10/A02_2021-Cryptographic_Failures

(unencrypted-socket-java)

netty-socketio-core/src/test/java/com/socketio4j/socketio/integration/DistributedRedissonStreamSingleChannelTest.java

[info] 46-46: "Detected use of a Java socket that is not encrypted. As a result, the
traffic could be read by an attacker intercepting the network traffic. Use
an SSLSocket created by 'SSLSocketFactory' or 'SSLServerSocketFactory'
instead."
Context: new ServerSocket(0)
Note: [CWE-319] Cleartext Transmission of Sensitive Information [REFERENCES]
- https://owasp.org/Top10/A02_2021-Cryptographic_Failures

(unencrypted-socket-java)

netty-socketio-core/src/test/java/com/socketio4j/socketio/integration/DistributedRedissonStreamMultiChannelTest.java

[info] 45-45: "Detected use of a Java socket that is not encrypted. As a result, the
traffic could be read by an attacker intercepting the network traffic. Use
an SSLSocket created by 'SSLSocketFactory' or 'SSLServerSocketFactory'
instead."
Context: new ServerSocket(0)
Note: [CWE-319] Cleartext Transmission of Sensitive Information [REFERENCES]
- https://owasp.org/Top10/A02_2021-Cryptographic_Failures

(unencrypted-socket-java)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (6)
  • GitHub Check: build (25) / build
  • GitHub Check: build (17) / build
  • GitHub Check: build (21) / build
  • GitHub Check: qodana
  • GitHub Check: Analyze (java-kotlin)
  • GitHub Check: Analyze (java-kotlin)
🔇 Additional comments (20)
netty-socketio-core/src/test/java/com/socketio4j/socketio/integration/DistributedRedissonStreamSingleChannelTest.java (5)

1-34: LGTM! Well-organized imports and licensing.

The Apache 2.0 license header is properly formatted, and all imports are relevant to the Redis Stream single-channel integration test setup.


37-42: LGTM! Appropriate test setup.

The use of PER_CLASS lifecycle and container reuse optimizes the test execution time for this distributed integration test. Separate Redisson clients for each node correctly simulate independent server instances.


46-50: Static analysis warning is a false positive.

The unencrypted socket warning from static analysis doesn't apply here. ServerSocket(0) is used solely to discover an available port number, not for actual network communication. This is a standard pattern in integration tests.

Note: There's a minor race condition where the port could be taken between discovery and server startup, but this is an accepted limitation in test environments.


107-111: LGTM! Clean configuration helper.

The redisConfig method provides a simple, focused configuration for single-server Redis connections appropriate for this integration test.


114-131: LGTM! Proper resource cleanup.

The cleanup method follows best practices with defensive null checks and proper shutdown order (servers → clients → container), ensuring all resources are released correctly even if initialization partially failed.

netty-socketio-core/src/test/java/com/socketio4j/socketio/integration/DistributedHazelcastRingBufferSingleChannelTest.java (3)

45-49: ServerSocket usage for port discovery is appropriate for tests.

The static analysis tool flags this as an unencrypted socket, but this is a false positive. The ServerSocket is only used to find an available port number and is immediately closed—it's not used for actual network communication.


54-110: Test setup correctly configures SINGLE_CHANNEL mode.

The setup properly instantiates two Hazelcast clients and configures both nodes with EventStoreMode.SINGLE_CHANNEL, which aligns with the test's purpose. The event listener registration and server startup sequence is correct.


113-131: Proper resource cleanup with defensive null checks.

The teardown method correctly stops servers, shuts down Hazelcast instances, and stops the container with appropriate null checks, ensuring clean resource release.

netty-socketio-core/src/test/java/com/socketio4j/socketio/integration/DistributedHazelcastRingBufferMultiChannelTest.java (3)

45-49: ServerSocket usage for port discovery is appropriate for tests.

The static analysis tool flags this as an unencrypted socket, but this is a false positive. The ServerSocket is only used to find an available port number and is immediately closed—it's not used for actual network communication.


54-110: Test setup correctly configures MULTI_CHANNEL mode.

The setup properly instantiates two Hazelcast clients and configures both nodes with EventStoreMode.MULTI_CHANNEL, which is the key difference from the SingleChannel variant. The event listener registration and server startup sequence is correct.


113-131: Proper resource cleanup with defensive null checks.

The teardown method correctly stops servers, shuts down Hazelcast instances, and stops the container with appropriate null checks, ensuring clean resource release.

netty-socketio-core/src/test/java/com/socketio4j/socketio/integration/DistributedRedissonStreamMultiChannelTest.java (4)

36-37: LGTM - Appropriate test class structure.

The PER_CLASS lifecycle annotation is correctly used here to allow instance-level setup/teardown methods for managing the Redis container and Redisson clients.


45-49: Port-finding utility is appropriate for local testing.

The static analysis warning about unencrypted sockets is a false positive here. This method uses ServerSocket(0) solely to discover an available port for local test servers—no sensitive data is transmitted. The socket is immediately closed via try-with-resources.


108-112: LGTM - Clean helper method.

The redisConfig helper provides a concise way to create Redisson configuration for single-server mode, appropriate for integration tests.


114-132: LGTM - Comprehensive cleanup with proper null checks.

The teardown method properly stops all resources in reverse order with defensive null checks, ensuring clean test isolation even if setup fails partially.

netty-socketio-core/src/test/java/com/socketio4j/socketio/store/HazelcastStoreFactoryTest.java (2)

56-72: LGTM! Factory setup correctly implements the new EventStore architecture.

The test properly configures the Hazelcast client with appropriate settings (setSmartRouting(false), setRedoOperation(true)) and wires the HazelcastEventStore using the builder pattern, aligning with the migration from PubSubStore to EventStore.


110-115: LGTM! Test correctly validates HazelcastEventStore.

The assertion for HazelcastEventStore.class matches the factory setup at lines 69-71, where HazelcastEventStore.Builder is used. This differs from the past review comment, which was for a different test file (HazelcastRingBufferStoreFactoryTest) that uses HazelcastRingBufferEventStore.

netty-socketio-core/src/main/java/com/socketio4j/socketio/store/hazelcast/HazelcastStoreFactory.java (3)

48-55: LGTM! Constructor properly validates required dependencies.

The constructor correctly enforces non-null constraints on both hazelcastClient and eventStore parameters, ensuring the factory is initialized with valid dependencies.


57-60: LGTM! Store and map creation methods correctly delegate to Hazelcast.

The createStore() and createMap() methods properly delegate to the underlying hazelcastClient, and the eventStore() getter correctly exposes the injected EventStore dependency.

Also applies to: 68-76


62-65: Clarify the lifecycle ownership model for HazelcastInstance in HazelcastStoreFactory.

The shutdown() method only closes the eventStore and does not shut down the hazelcastClient. However, this pattern is consistent across all StoreFactory implementations in the codebase—both RedissonStoreFactory and HazelcastStoreFactory follow the same approach of managing only the EventStore lifecycle while leaving the client lifecycle to the caller. This appears to be intentional design: the factory manages only what it creates (EventStore), while the caller who provides the client instance is responsible for managing its shutdown. If this differs from the previous implementation, clarify whether the design pattern should be changed for all StoreFactory implementations or if this represents the intended behavior.

node2.start();
port2 = cfg2.getPort();

//Thread.sleep(600);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Remove commented-out debug code.

The commented Thread.sleep(600) should be removed. If timing synchronization is genuinely needed, replace it with explicit waits or assertions rather than leaving it as a comment.

Apply this diff:

         node2.start();
         port2 = cfg2.getPort();
-
-        //Thread.sleep(600);
     }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
//Thread.sleep(600);
node2.start();
port2 = cfg2.getPort();
}
🤖 Prompt for AI Agents
In
netty-socketio-core/src/test/java/com/socketio4j/socketio/integration/DistributedRedissonStreamMultiChannelTest.java
around line 105, remove the commented-out debug line "//Thread.sleep(600)"; if
the test actually requires waiting for asynchronous behavior replace the
commented sleep with a proper explicit wait or assertion (e.g., wait for a
condition with a timeout or use existing test utilities to poll until expected
state) rather than leaving commented timing code.


/**
* Test class for HazelcastStoreFactory using testcontainers
* Test class for HazelcastRingBufferStoreFactory using testcontainers
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Update JavaDoc to reference the correct class name.

The comment mentions "HazelcastRingBufferStoreFactory" but this is the HazelcastStoreFactory test class.

Apply this diff:

-/**
- * Test class for HazelcastRingBufferStoreFactory using testcontainers
- */
+/**
+ * Test class for HazelcastStoreFactory using testcontainers
+ */
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
* Test class for HazelcastRingBufferStoreFactory using testcontainers
/**
* Test class for HazelcastStoreFactory using testcontainers
*/
🤖 Prompt for AI Agents
In
netty-socketio-core/src/test/java/com/socketio4j/socketio/store/HazelcastStoreFactoryTest.java
around line 47, the JavaDoc comment incorrectly references
"HazelcastRingBufferStoreFactory"; update the comment to reference the correct
class name "HazelcastStoreFactory" (or otherwise reflect this test class) so the
doc accurately matches the tested class.

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR introduces Redis Stream support with single and multi-channel capabilities, renames the PubSub abstraction to EventStore (reflecting that stores can be PubSub/Stream/Broker), and adds comprehensive distributed integration tests. The changes include architectural refactoring with new package structures (event, redis_pubsub, redis_reliable, hazelcast, hazelcast_ringbuffer, memory), introducing reliable/unreliable publishing modes, and enhancing test infrastructure with automatic retry mechanisms.

Key Changes:

  • Renamed PubSubStoreEventStore across the codebase with new event-based terminology
  • Added Redis Stream support via RedissonReliableEventStore with single/multi-channel modes
  • Introduced distributed integration tests covering multiple backend implementations (Redis PubSub, Redis Streams, Hazelcast)

Reviewed changes

Copilot reviewed 75 out of 76 changed files in this pull request and generated no comments.

Show a summary per file
File Description
pom.xml Added test retry configuration for flaky tests
module-info.java Exported new package structure (event, memory, redis_pubsub, etc.)
RedissonReliableEventStore.java New Redis Stream implementation with reliable delivery
RedissonEventStore.java Unreliable Redis PubSub implementation
RedissonStoreFactory.java Factory for Redis-based stores with EventStore injection
HazelcastEventStore.java Hazelcast PubSub EventStore implementation
HazelcastRingBufferEventStore.java Hazelcast RingBuffer (reliable) EventStore
BaseStoreFactory.java Refactored to support single/multi-channel event subscription
DistributedCommonTest.java Base class for distributed integration tests
Various test files Updated to use EventStore terminology and new package structure
Namespace.java Updated to use EventStore API
Configuration.java Updated JavaDoc references

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@sanjomo sanjomo merged commit b9a5c7c into main Dec 14, 2025
17 of 19 checks passed
@sanjomo sanjomo deleted the redis-stream-hz-ringbuffer branch December 14, 2025 07:07
@coderabbitai coderabbitai bot mentioned this pull request Dec 14, 2025
18 tasks
@NeatGuyCoding NeatGuyCoding added this to the 4.0.0 milestone Dec 14, 2025
@NeatGuyCoding NeatGuyCoding added the enhancement New feature or request label Dec 14, 2025
@coderabbitai coderabbitai bot mentioned this pull request Jan 5, 2026
18 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[FEATURE] Redis stream adapter

3 participants