Skip to content

Commit a873d79

Browse files
committed
Close the common pool when needed
1 parent c11cb42 commit a873d79

3 files changed

Lines changed: 262 additions & 201 deletions

File tree

Lines changed: 16 additions & 190 deletions
Original file line numberDiff line numberDiff line change
@@ -1,206 +1,32 @@
11
package it.tdlight;
22

3-
import it.tdlight.jni.TdApi;
4-
import it.tdlight.jni.TdApi.Object;
5-
import it.tdlight.util.UnsupportedNativeLibraryException;
6-
import it.tdlight.util.CleanSupport;
7-
import it.tdlight.util.CleanSupport.CleanableSupport;
8-
import java.util.ArrayList;
9-
import java.util.List;
10-
import java.util.Objects;
11-
import java.util.concurrent.locks.StampedLock;
12-
import org.slf4j.Logger;
13-
import org.slf4j.LoggerFactory;
3+
import it.tdlight.ClientFactoryImpl.CommonClientFactory;
144

15-
/**
16-
* TDLight client factory
17-
*/
18-
public class ClientFactory implements AutoCloseable {
5+
public interface ClientFactory extends AutoCloseable {
196

20-
private static final Logger logger = LoggerFactory.getLogger(ClientFactory.class);
21-
22-
private static volatile ClientFactory COMMON;
23-
24-
private final InternalClientsState state = new InternalClientsState() {
25-
@Override
26-
public void registerClient(int clientId, ClientEventsHandler internalClient) {
27-
startIfNeeded();
28-
super.registerClient(clientId, internalClient);
29-
responseReceiver.registerClient(clientId);
30-
}
31-
};
32-
33-
private final ResponseReceiver responseReceiver = new NativeResponseReceiver(this::handleClientEvents);
34-
private volatile CleanableSupport cleanable;
35-
36-
public static ClientFactory getCommonClientFactory() {
37-
ClientFactory common = COMMON;
7+
/**
8+
* Get the common client factory, start it if needed.
9+
* Remember to call clientFactory.close() afterward to release it!
10+
* @return Common client factory
11+
*/
12+
static ClientFactory acquireCommonClientFactory() {
13+
CommonClientFactory common = ClientFactoryImpl.COMMON;
3814
if (common == null) {
3915
synchronized (ClientFactory.class) {
40-
if (COMMON == null) {
41-
COMMON = new ClientFactory() {
42-
@Override
43-
public void close() {
44-
throw new UnsupportedOperationException("Common client factory can't be closed");
45-
}
46-
};
16+
if (ClientFactoryImpl.COMMON == null) {
17+
ClientFactoryImpl.COMMON = new CommonClientFactory();
4718
}
48-
common = COMMON;
19+
common = ClientFactoryImpl.COMMON;
4920
}
5021
}
22+
common.acquire();
5123
return common;
5224
}
5325

54-
public ClientFactory() {
55-
try {
56-
Init.init();
57-
} catch (UnsupportedNativeLibraryException e) {
58-
throw new RuntimeException("Can't load the client factory because TDLight can't be loaded", e);
59-
}
60-
}
61-
62-
public TelegramClient createClient() {
63-
return new AutoCleaningTelegramClient(state);
64-
}
65-
66-
public ReactiveTelegramClient createReactive() {
67-
return new InternalReactiveClient(state);
68-
}
69-
70-
public void startIfNeeded() {
71-
if (state.shouldStartNow()) {
72-
try {
73-
Init.init();
74-
responseReceiver.start();
75-
this.cleanable = CleanSupport.register(this, () -> {
76-
try {
77-
this.responseReceiver.close();
78-
} catch (InterruptedException e) {
79-
e.printStackTrace();
80-
}
81-
});
82-
state.setStarted();
83-
} catch (Throwable ex) {
84-
state.setStopped();
85-
logger.error("Failed to start TDLight", ex);
86-
}
87-
}
88-
}
26+
TelegramClient createClient();
8927

90-
private void handleClientEvents(int clientId,
91-
boolean isClosed,
92-
long[] clientEventIds,
93-
TdApi.Object[] clientEvents,
94-
int arrayOffset,
95-
int arrayLength) {
96-
StampedLock eventsHandlingLock = state.getEventsHandlingLock();
97-
boolean closeWriteLockAcquisitionFailed = false;
98-
long stamp = eventsHandlingLock.readLock();
99-
try {
100-
ClientEventsHandler handler = state.getClientEventsHandler(clientId);
101-
102-
if (handler != null) {
103-
handler.handleEvents(isClosed, clientEventIds, clientEvents, arrayOffset, arrayLength);
104-
} else {
105-
java.util.List<DroppedEvent> droppedEvents = getEffectivelyDroppedEvents(clientEventIds,
106-
clientEvents,
107-
arrayOffset,
108-
arrayLength
109-
);
110-
111-
if (!droppedEvents.isEmpty()) {
112-
logger.error("Unknown client id \"{}\"! {} events have been dropped!", clientId, droppedEvents.size());
113-
for (DroppedEvent droppedEvent : droppedEvents) {
114-
logger.error("The following event, with id \"{}\", has been dropped: {}",
115-
droppedEvent.id,
116-
droppedEvent.event
117-
);
118-
}
119-
}
120-
}
121-
122-
if (isClosed) {
123-
long writeLockStamp = eventsHandlingLock.tryConvertToWriteLock(stamp);
124-
if (writeLockStamp != 0L) {
125-
stamp = writeLockStamp;
126-
removeClientEventHandlers(clientId);
127-
} else {
128-
closeWriteLockAcquisitionFailed = true;
129-
}
130-
}
131-
} finally {
132-
eventsHandlingLock.unlock(stamp);
133-
}
134-
135-
if (closeWriteLockAcquisitionFailed) {
136-
stamp = eventsHandlingLock.writeLock();
137-
try {
138-
removeClientEventHandlers(clientId);
139-
} finally {
140-
eventsHandlingLock.unlockWrite(stamp);
141-
}
142-
}
143-
}
144-
145-
/**
146-
* Call this method only inside handleClientEvents!
147-
* Ensure that state has the eventsHandlingLock locked in write mode
148-
*/
149-
private void removeClientEventHandlers(int clientId) {
150-
logger.debug("Removing Client {} from event handlers", clientId);
151-
state.removeClientEventHandlers(clientId);
152-
logger.debug("Removed Client {} from event handlers", clientId);
153-
}
154-
155-
/**
156-
* Get only events that have been dropped, ignoring synthetic errors related to the closure of a client
157-
*/
158-
private List<DroppedEvent> getEffectivelyDroppedEvents(long[] clientEventIds,
159-
TdApi.Object[] clientEvents,
160-
int arrayOffset,
161-
int arrayLength) {
162-
java.util.List<DroppedEvent> droppedEvents = new ArrayList<>(arrayLength);
163-
for (int i = arrayOffset; i < arrayOffset + arrayLength; i++) {
164-
long id = clientEventIds[i];
165-
TdApi.Object event = clientEvents[i];
166-
boolean mustPrintError = true;
167-
if (event instanceof TdApi.Error) {
168-
TdApi.Error errorEvent = (TdApi.Error) event;
169-
if (Objects.equals("Request aborted", errorEvent.message)) {
170-
mustPrintError = false;
171-
}
172-
}
173-
if (mustPrintError) {
174-
droppedEvents.add(new DroppedEvent(id, event));
175-
}
176-
}
177-
return droppedEvents;
178-
}
179-
180-
protected void closeInternal() {
181-
if (state.shouldCloseNow()) {
182-
try {
183-
cleanable.clean();
184-
} catch (Throwable e) {
185-
logger.error("Failed to close", e);
186-
}
187-
this.state.setStopped();
188-
}
189-
}
28+
ReactiveTelegramClient createReactive();
19029

19130
@Override
192-
public void close() {
193-
this.closeInternal();
194-
}
195-
196-
private static final class DroppedEvent {
197-
198-
private final long id;
199-
private final TdApi.Object event;
200-
201-
private DroppedEvent(long id, Object event) {
202-
this.id = id;
203-
this.event = event;
204-
}
205-
}
31+
void close();
20632
}

0 commit comments

Comments
 (0)