Fix: cleanup JUL agent state on sessiond disconnect
[lttng-ust.git] / liblttng-ust-jul / org / lttng / ust / jul / LTTngTCPSessiondClient.java
CommitLineData
43e5396b
DG
1/*
2 * Copyright (C) 2013 - David Goulet <dgoulet@efficios.com>
3 *
4 * This library is free software; you can redistribute it and/or modify it
5 * under the terms of the GNU Lesser General Public License, version 2.1 only,
6 * as published by the Free Software Foundation.
7 *
8 * This library is distributed in the hope that it will be useful, but WITHOUT
9 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
10 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
11 * for more details.
12 *
13 * You should have received a copy of the GNU Lesser General Public License
14 * along with this library; if not, write to the Free Software Foundation,
15 * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
16 */
17
18package org.lttng.ust.jul;
19
20import java.util.concurrent.Semaphore;
21import java.nio.ByteBuffer;
22import java.nio.ByteOrder;
23import java.lang.Integer;
24import java.io.IOException;
25import java.io.BufferedOutputStream;
f1fa0535 26import java.io.BufferedReader;
43e5396b
DG
27import java.io.ByteArrayOutputStream;
28import java.io.DataOutputStream;
29import java.io.DataInputStream;
f1fa0535
DG
30import java.io.FileReader;
31import java.io.FileNotFoundException;
43e5396b
DG
32import java.net.*;
33import java.lang.management.ManagementFactory;
34import java.util.ArrayList;
529e6def 35import java.util.HashMap;
e614d916
JG
36import java.util.HashSet;
37import java.util.Iterator;
43e5396b 38import java.util.List;
e614d916 39import java.util.Set;
43e5396b
DG
40import java.util.Timer;
41import java.util.TimerTask;
529e6def 42import java.util.logging.Logger;
e614d916 43import java.util.Collections;
43e5396b
DG
44
45class USTRegisterMsg {
46 public static int pid;
47}
48
49public class LTTngTCPSessiondClient {
50 /* Command header from the session deamon. */
51 private LTTngSessiondCmd2_4.sessiond_hdr headerCmd =
52 new LTTngSessiondCmd2_4.sessiond_hdr();
53
54 private final String sessiondHost;
43e5396b
DG
55 private Socket sessiondSock;
56 private boolean quit = false;
57
58 private DataInputStream inFromSessiond;
59 private DataOutputStream outToSessiond;
60
61 private LTTngLogHandler handler;
62
63 private Semaphore registerSem;
64
65 private Timer eventTimer;
e614d916
JG
66 private Set<LTTngEvent> enabledEventSet =
67 Collections.synchronizedSet(new HashSet<LTTngEvent>());
529e6def
DG
68 /*
69 * Map of Logger objects that have been enabled. They are indexed by name.
70 */
71 private HashMap<String, Logger> enabledLoggers = new HashMap<String, Logger>();
43e5396b
DG
72 /* Timer delay at each 5 seconds. */
73 private final static long timerDelay = 5 * 1000;
74 private static boolean timerInitialized;
75
f1fa0535
DG
76 private static final String rootPortFile = "/var/run/lttng/jul.port";
77 private static final String userPortFile = "/.lttng/jul.port";
78
79 /* Indicate if we've already release the semaphore. */
80 private boolean sem_posted = false;
81
82 public LTTngTCPSessiondClient(String host, Semaphore sem) {
43e5396b 83 this.sessiondHost = host;
43e5396b
DG
84 this.registerSem = sem;
85 this.eventTimer = new Timer();
86 this.timerInitialized = false;
87 }
88
89 private void setupEventTimer() {
90 if (this.timerInitialized) {
91 return;
92 }
93
94 this.eventTimer.scheduleAtFixedRate(new TimerTask() {
95 @Override
96 public void run() {
e614d916
JG
97 synchronized (enabledEventSet) {
98 LTTngSessiondCmd2_4.sessiond_enable_handler enableCmd = new
99 LTTngSessiondCmd2_4.sessiond_enable_handler();
529e6def 100 /*
e614d916
JG
101 * Modifying events in a Set will raise a
102 * ConcurrentModificationException. Thus, we remove an event
103 * and add its modified version to modifiedEvents when a
104 * modification is necessary.
529e6def 105 */
e614d916
JG
106 Set<LTTngEvent> modifiedEvents = new HashSet<LTTngEvent>();
107 Iterator<LTTngEvent> it = enabledEventSet.iterator();
5b5ffa03 108
e614d916
JG
109 while (it.hasNext()) {
110 int ret;
111 Logger logger;
112 LTTngEvent event = it.next();
5b5ffa03 113
5b5ffa03 114 /*
e614d916
JG
115 * Check if this Logger name has been enabled already. Note
116 * that in the case of "*", it's never added in that hash
117 * table thus the enable command does a lookup for each
118 * logger name in that hash table for the * case in order
119 * to make sure we don't enable twice the same logger
120 * because JUL apparently accepts that the *same*
121 * LogHandler can be added twice on a Logger object...
122 * don't ask...
5b5ffa03 123 */
e614d916
JG
124 logger = enabledLoggers.get(event.name);
125 if (logger != null) {
126 continue;
127 }
529e6def 128
e614d916
JG
129 /*
130 * Set to one means that the enable all event has been seen
131 * thus event from that point on must use loglevel for all
132 * events. Else the object has its own loglevel.
133 */
134 if (handler.logLevelUseAll == 1) {
135 it.remove();
3c3a0129 136 event.logLevels.addAll(handler.logLevelsAll);
e614d916
JG
137 modifiedEvents.add(event);
138 }
139
140 /*
141 * The all event is a special case since we have to iterate
142 * over every Logger to see which one was not enabled.
143 */
144 if (event.name.equals("*")) {
145 enableCmd.name = event.name;
3c3a0129
DG
146 /* Tell the command NOT to add the loglevel. */
147 enableCmd.lttngLogLevel = -1;
e614d916
JG
148 /*
149 * The return value is irrelevant since the * event is
150 * always kept in the set.
151 */
152 enableCmd.execute(handler, enabledLoggers);
153 continue;
154 }
155
156 ret = enableCmd.enableLogger(handler, event, enabledLoggers);
157 if (ret == 1) {
158 /* Enabled so remove the event from the set. */
159 if (!modifiedEvents.remove(event)) {
160 /*
161 * event can only be present in one of
162 * the sets.
163 */
164 it.remove();
165 }
166 }
43e5396b 167 }
e614d916 168 enabledEventSet.addAll(modifiedEvents);
43e5396b 169 }
e614d916 170
43e5396b
DG
171 }
172 }, this.timerDelay, this.timerDelay);
173
174 this.timerInitialized = true;
175 }
176
f1fa0535
DG
177 /*
178 * Try to release the registerSem if it's not already done.
179 */
180 private void tryReleaseSem()
181 {
182 /* Release semaphore so we unblock the agent. */
183 if (!this.sem_posted) {
184 this.registerSem.release();
185 this.sem_posted = true;
186 }
187 }
188
b4995733
DG
189 /*
190 * Cleanup Agent state.
191 */
192 private void cleanupState() {
193 enabledEventSet.clear();
194 enabledLoggers.clear();
195 if (this.handler != null) {
196 this.handler.clear();
197 }
198 }
199
43e5396b
DG
200 public void init(LTTngLogHandler handler) throws InterruptedException {
201 this.handler = handler;
202
203 for (;;) {
204 if (this.quit) {
205 break;
206 }
207
b4995733
DG
208 /* Cleanup Agent state before trying to connect or reconnect. */
209 cleanupState();
210
43e5396b
DG
211 try {
212
213 /*
214 * Connect to the session daemon before anything else.
215 */
216 connectToSessiond();
217
218 /*
219 * Register to the session daemon as the Java component of the
220 * UST application.
221 */
222 registerToSessiond();
43e5396b
DG
223
224 setupEventTimer();
225
226 /*
227 * Block on socket receive and wait for command from the
228 * session daemon. This will return if and only if there is a
229 * fatal error or the socket closes.
230 */
231 handleSessiondCmd();
232 } catch (UnknownHostException uhe) {
f1fa0535 233 tryReleaseSem();
43e5396b
DG
234 System.out.println(uhe);
235 } catch (IOException ioe) {
f1fa0535 236 tryReleaseSem();
43e5396b
DG
237 Thread.sleep(3000);
238 } catch (Exception e) {
f1fa0535 239 tryReleaseSem();
43e5396b
DG
240 e.printStackTrace();
241 }
242 }
243 }
244
245 public void destroy() {
246 this.quit = true;
247 this.eventTimer.cancel();
248
249 try {
250 if (this.sessiondSock != null) {
251 this.sessiondSock.close();
252 }
253 } catch (Exception e) {
254 e.printStackTrace();
255 }
256 }
257
258 /*
259 * Receive header data from the session daemon using the LTTng command
260 * static buffer of the right size.
261 */
262 private void recvHeader() throws Exception {
263 int read_len;
264 byte data[] = new byte[this.headerCmd.SIZE];
265
266 read_len = this.inFromSessiond.read(data, 0, data.length);
267 if (read_len != data.length) {
268 throw new IOException();
269 }
270 this.headerCmd.populate(data);
271 }
272
273 /*
274 * Receive payload from the session daemon. This MUST be done after a
275 * recvHeader() so the header value of a command are known.
276 *
277 * The caller SHOULD use isPayload() before which returns true if a payload
278 * is expected after the header.
279 */
280 private byte[] recvPayload() throws Exception {
281 byte payload[] = new byte[(int) this.headerCmd.data_size];
282
283 /* Failsafe check so we don't waste our time reading 0 bytes. */
284 if (payload.length == 0) {
285 return null;
286 }
287
288 this.inFromSessiond.read(payload, 0, payload.length);
289 return payload;
290 }
291
292 /*
293 * Handle session command from the session daemon.
294 */
295 private void handleSessiondCmd() throws Exception {
296 int ret_code;
297 byte data[] = null;
298
299 while (true) {
300 /* Get header from session daemon. */
301 recvHeader();
302
303 if (headerCmd.data_size > 0) {
304 data = recvPayload();
305 }
306
307 switch (headerCmd.cmd) {
f08bb871
DG
308 case CMD_REG_DONE:
309 {
310 /*
311 * Release semaphore so meaning registration is done and we
312 * can proceed to continue tracing.
313 */
f1fa0535 314 tryReleaseSem();
9aabed2d
DG
315 /*
316 * We don't send any reply to the registration done command.
317 * This just marks the end of the initial session setup.
318 */
319 continue;
f08bb871 320 }
43e5396b
DG
321 case CMD_LIST:
322 {
323 LTTngSessiondCmd2_4.sessiond_list_logger listLoggerCmd =
324 new LTTngSessiondCmd2_4.sessiond_list_logger();
325 listLoggerCmd.execute(this.handler);
326 data = listLoggerCmd.getBytes();
327 break;
328 }
329 case CMD_ENABLE:
330 {
5b5ffa03 331 LTTngEvent event;
43e5396b
DG
332 LTTngSessiondCmd2_4.sessiond_enable_handler enableCmd =
333 new LTTngSessiondCmd2_4.sessiond_enable_handler();
334 if (data == null) {
335 enableCmd.code = LTTngSessiondCmd2_4.lttng_jul_ret_code.CODE_INVALID_CMD;
336 break;
337 }
338 enableCmd.populate(data);
5b5ffa03
DG
339 event = enableCmd.execute(this.handler, this.enabledLoggers);
340 if (event != null) {
43e5396b 341 /*
e614d916 342 * Add the event to the set so it can be enabled if
43e5396b
DG
343 * the logger appears at some point in time.
344 */
e614d916 345 enabledEventSet.add(event);
43e5396b
DG
346 }
347 data = enableCmd.getBytes();
348 break;
349 }
350 case CMD_DISABLE:
351 {
352 LTTngSessiondCmd2_4.sessiond_disable_handler disableCmd =
353 new LTTngSessiondCmd2_4.sessiond_disable_handler();
354 if (data == null) {
355 disableCmd.code = LTTngSessiondCmd2_4.lttng_jul_ret_code.CODE_INVALID_CMD;
356 break;
357 }
358 disableCmd.populate(data);
359 disableCmd.execute(this.handler);
360 data = disableCmd.getBytes();
361 break;
362 }
363 default:
364 {
365 data = new byte[4];
366 ByteBuffer buf = ByteBuffer.wrap(data);
367 buf.order(ByteOrder.BIG_ENDIAN);
368 LTTngSessiondCmd2_4.lttng_jul_ret_code code =
369 LTTngSessiondCmd2_4.lttng_jul_ret_code.CODE_INVALID_CMD;
370 buf.putInt(code.getCode());
371 break;
372 }
373 }
374
375 /* Send payload to session daemon. */
376 this.outToSessiond.write(data, 0, data.length);
377 this.outToSessiond.flush();
378 }
379 }
380
f1fa0535
DG
381 private String getHomePath() {
382 return System.getProperty("user.home");
383 }
384
385 /**
386 * Read port number from file created by the session daemon.
387 *
388 * @return port value if found else 0.
389 */
390 private int getPortFromFile(String path) throws IOException {
391 int port;
392 BufferedReader br;
393
394 try {
395 br = new BufferedReader(new FileReader(path));
396 String line = br.readLine();
397 port = Integer.parseInt(line, 10);
398 if (port < 0 || port > 65535) {
399 /* Invalid value. Ignore. */
400 port = 0;
401 }
402 br.close();
403 } catch (FileNotFoundException e) {
404 /* No port available. */
405 port = 0;
406 }
407
408 return port;
409 }
410
43e5396b 411 private void connectToSessiond() throws Exception {
f1fa0535
DG
412 int port;
413
414 if (this.handler.is_root == 1) {
415 port = getPortFromFile(rootPortFile);
416 if (port == 0) {
417 /* No session daemon available. Stop and retry later. */
418 throw new IOException();
419 }
420 } else {
421 port = getPortFromFile(getHomePath() + userPortFile);
422 if (port == 0) {
423 /* No session daemon available. Stop and retry later. */
424 throw new IOException();
425 }
426 }
427
428 this.sessiondSock = new Socket(this.sessiondHost, port);
43e5396b
DG
429 this.inFromSessiond = new DataInputStream(
430 sessiondSock.getInputStream());
431 this.outToSessiond = new DataOutputStream(
432 sessiondSock.getOutputStream());
433 }
434
435 private void registerToSessiond() throws Exception {
436 byte data[] = new byte[4];
437 ByteBuffer buf = ByteBuffer.wrap(data);
438 String pid = ManagementFactory.getRuntimeMXBean().getName().split("@")[0];
439
440 buf.putInt(Integer.parseInt(pid));
441 this.outToSessiond.write(data, 0, data.length);
442 this.outToSessiond.flush();
443 }
444}
This page took 0.043111 seconds and 4 git commands to generate.