Skip to content

Commit ed0804b

Browse files
committed
Add nullability information to EventManager
Move thread to dispatcher as they are intimately tied.
1 parent b4deb3b commit ed0804b

3 files changed

Lines changed: 85 additions & 36 deletions

File tree

src/main/org/firebirdsql/event/EventManager.java

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
11
// SPDX-FileCopyrightText: Copyright 2005 Gabriel Reid
2-
// SPDX-FileCopyrightText: Copyright 2011-2023 Mark Rotteveel
2+
// SPDX-FileCopyrightText: Copyright 2011-2026 Mark Rotteveel
33
// SPDX-License-Identifier: LGPL-2.1-or-later OR BSD-3-Clause
44
package org.firebirdsql.event;
55

66
import org.firebirdsql.gds.ng.WireCrypt;
77
import org.firebirdsql.jaybird.props.AttachmentProperties;
88
import org.firebirdsql.jaybird.props.DatabaseConnectionProperties;
9+
import org.jspecify.annotations.NullMarked;
10+
import org.jspecify.annotations.Nullable;
911

1012
import java.sql.SQLException;
1113

@@ -16,6 +18,7 @@
1618
* @author Mark Rotteveel
1719
*/
1820
@SuppressWarnings("unused")
21+
@NullMarked
1922
public interface EventManager extends AttachmentProperties, AutoCloseable {
2023

2124
/**
@@ -67,7 +70,7 @@ public interface EventManager extends AttachmentProperties, AutoCloseable {
6770
* @return database name
6871
* @since 5
6972
*/
70-
String getDatabaseName();
73+
@Nullable String getDatabaseName();
7174

7275
/**
7376
* Set the database name.

src/main/org/firebirdsql/event/FBEventManager.java

Lines changed: 77 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
/*
22
SPDX-FileCopyrightText: Copyright 2005 Gabriel Reid
33
SPDX-FileCopyrightText: Copyright 2006-2008 Roman Rokytskyy
4-
SPDX-FileCopyrightText: Copyright 2011-2024 Mark Rotteveel
4+
SPDX-FileCopyrightText: Copyright 2011-2026 Mark Rotteveel
55
SPDX-FileCopyrightText: Copyright 2019 Vasiliy Yashkov
66
SPDX-License-Identifier: LGPL-2.1-or-later OR BSD-3-Clause
77
*/
@@ -19,6 +19,8 @@
1919
import org.firebirdsql.jaybird.util.SQLExceptionChainBuilder;
2020
import org.firebirdsql.jaybird.xca.FatalErrorHelper;
2121
import org.firebirdsql.jdbc.FirebirdConnection;
22+
import org.jspecify.annotations.NullMarked;
23+
import org.jspecify.annotations.Nullable;
2224

2325
import java.sql.Connection;
2426
import java.sql.SQLException;
@@ -32,6 +34,7 @@
3234
import static java.lang.System.Logger.Level.DEBUG;
3335
import static java.lang.System.Logger.Level.ERROR;
3436
import static java.lang.System.Logger.Level.WARNING;
37+
import static java.util.Objects.requireNonNull;
3538

3639
/**
3740
* An {@link org.firebirdsql.event.EventManager} implementation to listen for database events.
@@ -40,22 +43,22 @@
4043
* @author Mark Rotteveel
4144
* @author Vasiliy Yashkov
4245
*/
46+
@NullMarked
4347
public class FBEventManager implements EventManager {
4448

4549
private static final System.Logger log = System.getLogger(FBEventManager.class.getName());
4650

4751
private final Lock lock = new ReentrantLock();
4852
private final LockCloseable unlock = lock::unlock;
4953
private final GDSType gdsType;
50-
private FbDatabase fbDatabase;
54+
private @Nullable FbDatabase fbDatabase;
5155
private final IConnectionProperties connectionProperties;
5256
private final EventManagerBehaviour eventManagerBehaviour;
5357
private volatile boolean connected = false;
5458
private final Map<String, Set<EventListener>> listenerMap = new HashMap<>();
5559
private final Map<String, GdsEventHandler> handlerMap = new HashMap<>();
5660
private final BlockingQueue<DatabaseEvent> eventQueue = new LinkedBlockingQueue<>();
57-
private EventDispatcher eventDispatcher;
58-
private Thread dispatchThread;
61+
private @Nullable EventDispatcher eventDispatcher;
5962
private volatile long waitTimeout = 1000;
6063
private final DbListener dbListener = new DbListener();
6164

@@ -78,8 +81,8 @@ public FBEventManager(GDSType gdsType) {
7881
*/
7982
private FBEventManager(Connection connection) throws SQLException {
8083
this.fbDatabase = connection.unwrap(FirebirdConnection.class).getFbDatabase();
81-
gdsType = null;
8284
connectionProperties = fbDatabase.getConnectionProperties().asImmutable();
85+
gdsType = GDSType.getType(connectionProperties.getType());
8386
eventManagerBehaviour = new ManagedEventManagerBehaviour();
8487
// NOTE In this implementation, we don't take into account pooled connections that might be closed while
8588
// the FbDatabase instance remains in use. This means that at the moment, it is possible that the event manager
@@ -127,9 +130,7 @@ public void connect() throws SQLException {
127130
connected = true;
128131

129132
eventDispatcher = new EventDispatcher();
130-
dispatchThread = new Thread(eventDispatcher);
131-
dispatchThread.setDaemon(true);
132-
dispatchThread.start();
133+
eventDispatcher.start();
133134
}
134135
}
135136

@@ -182,17 +183,13 @@ public void disconnect() throws SQLException {
182183
private void terminateDispatcher() throws SQLException {
183184
EventDispatcher eventDispatcher = this.eventDispatcher;
184185
if (eventDispatcher != null) {
185-
eventDispatcher.stop();
186-
dispatchThread.interrupt();
187-
// join the thread and wait until it dies
188186
try {
189-
dispatchThread.join();
187+
eventDispatcher.stopAndWait();
190188
} catch (InterruptedException ex) {
191189
Thread.currentThread().interrupt();
192190
throw new SQLException(ex);
193191
} finally {
194192
this.eventDispatcher = null;
195-
dispatchThread = null;
196193
}
197194
}
198195
}
@@ -327,9 +324,8 @@ public void addEventListener(String eventName, EventListener listener) throws SQ
327324

328325
@Override
329326
public void removeEventListener(String eventName, EventListener listener) throws SQLException {
330-
if (eventName == null || listener == null) {
331-
throw new NullPointerException();
332-
}
327+
requireNonNull(eventName, "eventName");
328+
requireNonNull(listener, "listener");
333329
try (LockCloseable ignored = withLock()) {
334330
Set<EventListener> listenerSet = listenerMap.get(eventName);
335331
if (listenerSet != null) {
@@ -349,9 +345,7 @@ public int waitForEvent(String eventName) throws InterruptedException, SQLExcept
349345

350346
@Override
351347
public int waitForEvent(String eventName, final int timeout) throws InterruptedException, SQLException {
352-
if (eventName == null) {
353-
throw new NullPointerException();
354-
}
348+
requireNonNull(eventName, "eventName");
355349
if (!connected) {
356350
throw new IllegalStateException("Can't wait for events with disconnected EventManager");
357351
}
@@ -420,12 +414,14 @@ public Map<ConnectionProperty, Object> connectionPropertyValues() {
420414
}
421415

422416
private void addDbListeners(FbDatabase database) {
417+
//noinspection ConstantValue : null check for robustness
423418
if (database == null) return;
424419
database.addDatabaseListener(dbListener);
425420
database.addExceptionListener(dbListener);
426421
}
427422

428423
private void removeDbListeners(FbDatabase database) {
424+
//noinspection ConstantValue : null check for robustness
429425
if (database == null) return;
430426
database.removeExceptionListener(dbListener);
431427
database.removeDatabaseListener(dbListener);
@@ -439,7 +435,7 @@ private void removeDbListeners(FbDatabase database) {
439435
*
440436
* @return underlying {@link FbDatabase}
441437
*/
442-
FbDatabase getFbDatabase() {
438+
@Nullable FbDatabase getFbDatabase() {
443439
return fbDatabase;
444440
}
445441

@@ -465,8 +461,11 @@ public void connectDatabase() throws SQLException {
465461

466462
@Override
467463
public void disconnectDatabase() throws SQLException {
468-
removeDbListeners(fbDatabase);
469-
fbDatabase.close();
464+
FbDatabase fbDatabase = FBEventManager.this.fbDatabase;
465+
if (fbDatabase != null) {
466+
removeDbListeners(fbDatabase);
467+
fbDatabase.close();
468+
}
470469
}
471470

472471
@Override
@@ -480,7 +479,7 @@ public void handleDetaching(FbDatabase database) {
480479

481480
@Override
482481
public void handleErrorOccurred(Object source, SQLException exception) {
483-
FbDatabase fbDatabase = FBEventManager.this.fbDatabase;
482+
FbDatabase fbDatabase = getFbDatabase();
484483
if (fbDatabase == null || !fbDatabase.isAttached()) return;
485484

486485
if (FatalErrorHelper.isBrokenConnection(exception)) {
@@ -575,19 +574,19 @@ final class GdsEventHandler implements org.firebirdsql.gds.EventHandler {
575574
private volatile boolean cancelled = false;
576575

577576
GdsEventHandler(String eventName) throws SQLException {
578-
eventHandle = fbDatabase.createEventHandle(eventName, this);
577+
eventHandle = requireNonNull(fbDatabase, "fbDatabase").createEventHandle(eventName, this);
579578
}
580579

581580
void register() throws SQLException {
582581
if (cancelled) {
583582
throw new IllegalStateException("Trying to register a cancelled event handler");
584583
}
585-
fbDatabase.queueEvent(eventHandle);
584+
requireNonNull(fbDatabase, "fbDatabase").queueEvent(eventHandle);
586585
}
587586

588587
void unregister() throws SQLException {
589588
if (cancelled) return;
590-
fbDatabase.cancelEvent(eventHandle);
589+
requireNonNull(fbDatabase, "fbDatabase").cancelEvent(eventHandle);
591590
cancelled = true;
592591
}
593592

@@ -597,7 +596,7 @@ public void eventOccurred(EventHandle eventHandle) {
597596
return;
598597
}
599598
try {
600-
fbDatabase.countEvents(eventHandle);
599+
requireNonNull(fbDatabase, "fbDatabase").countEvents(eventHandle);
601600
} catch (SQLException e) {
602601
log.log(WARNING, "Exception processing event counts; see debug level for stacktrace");
603602
log.log(DEBUG, "Exception processing event counts", e);
@@ -620,12 +619,59 @@ public void eventOccurred(EventHandle eventHandle) {
620619

621620
final class EventDispatcher implements Runnable {
622621

623-
private volatile boolean running = true;
622+
private @Nullable Thread thread;
623+
private volatile boolean running;
624+
625+
/**
626+
* Resets the running state if the dispatcher was previously stopped, and starts a new thread for the event
627+
* dispatcher.
628+
*
629+
* @throws IllegalStateException
630+
* if this dispatcher already has a thread, and that thread is still alive
631+
* @see #stop()
632+
* @see #stopAndWait()
633+
* @since 7
634+
*/
635+
void start() {
636+
synchronized (this) {
637+
if (thread != null && thread.isAlive()) {
638+
throw new IllegalStateException("EventDispatcher is already running");
639+
}
640+
var thread = this.thread = new Thread(this);
641+
thread.setDaemon(true);
642+
running = true;
643+
thread.start();
644+
}
645+
}
624646

647+
/**
648+
* Stops the event dispatcher.
649+
*
650+
* @see #stopAndWait()
651+
*/
625652
void stop() {
626653
running = false;
627654
}
628655

656+
/**
657+
* Stops the event dispatcher, interrupts the dispatcher thread, and waits for it to end.
658+
*
659+
* @throws InterruptedException
660+
* if the call to {@link Thread#join()} throws
661+
* @since 7
662+
*/
663+
void stopAndWait() throws InterruptedException {
664+
stop();
665+
Thread thread;
666+
synchronized (this) {
667+
thread = this.thread;
668+
}
669+
if (thread != null) {
670+
thread.interrupt();
671+
thread.join();
672+
}
673+
}
674+
629675
@Override
630676
public void run() {
631677
while (running) {
@@ -642,11 +688,9 @@ public void run() {
642688

643689
private void notify(DatabaseEvent event) {
644690
try (LockCloseable ignored = withLock()) {
645-
Set<EventListener> listenerSet = listenerMap.get(event.getEventName());
646-
if (listenerSet != null) {
647-
for (EventListener listener : listenerSet) {
648-
listener.eventOccurred(event);
649-
}
691+
Set<EventListener> listenerSet = listenerMap.getOrDefault(event.getEventName(), Set.of());
692+
for (EventListener listener : listenerSet) {
693+
listener.eventOccurred(event);
650694
}
651695
}
652696
}

src/test/org/firebirdsql/event/FBEventManagerTest.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
// SPDX-FileCopyrightText: Copyright 2005 Gabriel Reid
22
// SPDX-FileCopyrightText: Copyright 2006-2008 Roman Rokytskyy
3-
// SPDX-FileCopyrightText: Copyright 2012-2024 Mark Rotteveel
3+
// SPDX-FileCopyrightText: Copyright 2012-2026 Mark Rotteveel
44
// SPDX-License-Identifier: LGPL-2.1-or-later
55
package org.firebirdsql.event;
66

@@ -381,11 +381,13 @@ void testDefaultEventManagerDisconnectionOnDbClose() throws Exception {
381381
setupDefaultEventManager();
382382
FbDatabase db = ((FBEventManager) eventManager).getFbDatabase();
383383

384+
assertNotNull(db, "expected non-null FbDatabase");
384385
assertTrue(eventManager.isConnected(), "expected connected event manager");
385386

386387
db.close();
387388

388389
assertFalse(eventManager.isConnected(), "expected disconnected event manager");
390+
assertNull(((FBEventManager) eventManager).getFbDatabase(), "expected null FbDatabase after close");
389391
}
390392

391393
private class EventWait implements Callable<Integer> {

0 commit comments

Comments
 (0)