@@ -12,7 +12,7 @@ TEST(Timer, CreateTimer) {
1212#endif
1313
1414 atomic<int > count{0 };
15- Address addr{" 127.0.0.1" , 8080 };
15+ Address addr{" 127.0.0.1" , 19981 };
1616 TcpServer server{addr};
1717 auto rc = server.Init ();
1818 MUST_TRUE (rc == 0 , server.err_msg ());
@@ -62,3 +62,80 @@ TEST(Timer, CreateTimer) {
6262 " timer event count wrong " + to_string (count));
6363#endif
6464}
65+
66+ TEST (Timer, ResetInPlace) {
67+ #ifdef WIN32
68+ SKIP ();
69+ #endif
70+
71+ // Verifies that Reset() updates the timer interval *in place* without
72+ // recreating the underlying fd. If a regression makes Reset() recreate
73+ // the fd, the new fd is not registered in epoll/kqueue and count_after
74+ // would stay zero.
75+ atomic<int > count_before{0 };
76+ atomic<int > count_after{0 };
77+ atomic<bool > reset_done{false };
78+
79+ Address addr{" 127.0.0.1" , 19981 };
80+ TcpServer server{addr};
81+ auto rc = server.Init ();
82+ MUST_TRUE (rc == 0 , server.err_msg ());
83+
84+ TimerSocket timerfd (0 , 1e6 ); // 1ms
85+ MUST_TRUE (timerfd.status () != cppnet::Socket::kUninit , " create timerfd" );
86+ DEFER_DEFAULT { timerfd.Close (); };
87+
88+ int original_fd = timerfd.fd ();
89+
90+ rc = server.AddSoc (timerfd);
91+ MUST_TRUE (rc == 0 , server.err_msg ());
92+
93+ server.Register ([&](TcpServer::Event event, TcpServer &, Socket fd) {
94+ if (event == TcpServer::kEventRead ) {
95+ uint64_t exp;
96+ TimerSocket (fd).Read (&exp, sizeof (uint64_t ));
97+ if (reset_done.load ()) {
98+ count_after.fetch_add (1 );
99+ } else {
100+ count_before.fetch_add (1 );
101+ }
102+ }
103+ });
104+
105+ GO ([&]() {
106+ usleep (2e4 ); // collect events with 1ms interval for ~20ms
107+ timerfd.Reset (0 , 2e6 ); // slow down to 2ms
108+ MUST_TRUE (timerfd.fd () == original_fd,
109+ " Reset must not recreate fd: got " + to_string (timerfd.fd ()) +
110+ " , expected " + to_string (original_fd));
111+ reset_done.store (true );
112+ usleep (2e4 ); // observe events under the new interval for ~20ms
113+ server.Stop ();
114+ });
115+
116+ rc = server.EventLoop ();
117+ MUST_TRUE (rc == 0 , server.err_msg ());
118+
119+ DEBUG (" ResetInPlace: before=" << count_before << " after=" << count_after);
120+ MUST_TRUE (count_before > 0 ,
121+ " no events before Reset, count=" + to_string (count_before));
122+ MUST_TRUE (count_after > 0 ,
123+ " no events after Reset (fd likely recreated and dropped from "
124+ " io_multiplexing), count=" +
125+ to_string (count_after));
126+ }
127+
128+ TEST (Timer, CloseIdempotent) {
129+ #ifdef WIN32
130+ SKIP ();
131+ #endif
132+
133+ // Multiple Close() calls must not crash, double-free or leave dangling
134+ // resources. Memory issues here are caught by CI's valgrind run.
135+ TimerSocket timerfd (0 , 1e6 );
136+ MUST_TRUE (timerfd.status () != cppnet::Socket::kUninit , " create timerfd" );
137+
138+ timerfd.Close ();
139+ timerfd.Close ();
140+ timerfd.Close ();
141+ }
0 commit comments