001package jmri.jmrix; 002 003import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; 004import java.io.DataInputStream; 005import java.io.IOException; 006import java.io.OutputStream; 007import java.util.*; 008 009import javax.annotation.Nonnull; 010import javax.swing.SwingUtilities; 011 012import jmri.InstanceManager; 013import jmri.ShutDownManager; 014 015/** 016 * Abstract base for TrafficControllers in a Message/Reply protocol. 017 * <p> 018 * Two threads are used for the actual communication. The "Transmit" thread 019 * handles pushing characters to the port, and also changing the mode. The 020 * "Receive" thread converts characters from the input stream into replies. 021 * <p> 022 * The constructor registers a shutdown task to 023 * trigger the necessary cleanup code 024 * <p> 025 * The internal state machine handles changes of mode, automatic retry of 026 * certain messages, time outs, and sending poll messages when otherwise idle. 027 * <p> 028 * "Mode" refers to the state of the command station communications. "Normal" 029 * and "Programming" are the two modes, used if the command station requires 030 * messages to go back and forth between them. <br> 031 * 032 * <img src="doc-files/AbstractMRTrafficController-StateDiagram.png" alt="UML State diagram"> 033 * 034 * <p> 035 * The key methods for the basic operation are: 036 * <ul> 037 * <li>If needed for formatting outbound messages, {@link #addHeaderToOutput(byte[], AbstractMRMessage)} and {@link #addTrailerToOutput(byte[], int, AbstractMRMessage)} 038 * <li> {@link #newReply()} creates an empty reply message (of the proper concrete type) to fill with incoming data 039 * <li>The {@link #endOfMessage(AbstractMRReply) } method is used to parse incoming messages. If it needs 040 * information on e.g. the last message sent, that can be stored in member variables 041 * by {@link #forwardToPort(AbstractMRMessage, AbstractMRListener)}. 042 * <li>{@link #forwardMessage(AbstractMRListener, AbstractMRMessage)} and {@link #forwardReply(AbstractMRListener, AbstractMRReply) } handle forwarding of specific types of objects 043 * </ul> 044 * <p> 045 * If your command station requires messages to go in and out of 046 * "programming mode", those should be provided by 047 * {@link #enterProgMode()} and {@link #enterNormalMode()}. 048 * <p> 049 * If you want to poll for information when the line is otherwise idle, 050 * implement {@link #pollMessage()} and {@link #pollReplyHandler()}. 051 * 052 * @author Bob Jacobsen Copyright (C) 2003 053 * @author Paul Bender Copyright (C) 2004-2010 054 */ 055 056/* 057@startuml jmri/jmrix/doc-files/AbstractMRTrafficController-StateDiagram.png 058 059 [*] --> IDLESTATE 060 IDLESTATE --> NOTIFIEDSTATE : sendMessage() 061 NOTIFIEDSTATE --> IDLESTATE : queue empty 062 063 NOTIFIEDSTATE --> WAITMSGREPLYSTATE : transmitLoop()\nwake, send message 064 065 WAITMSGREPLYSTATE --> WAITREPLYINPROGMODESTATE : transmitLoop()\nnot in PROGRAMINGMODE,\nmsg for PROGRAMINGMODE 066 WAITMSGREPLYSTATE --> WAITREPLYINNORMMODESTATE : transmitLoop()\nnot in NORMALMODE,\nmsg for NORMALMODE 067 068 WAITMSGREPLYSTATE --> NOTIFIEDSTATE : handleOneIncomingReply() 069 070 WAITREPLYINPROGMODESTATE --> OKSENDMSGSTATE : handleOneIncomingReply()\nentered PROGRAMINGMODE 071 WAITREPLYINNORMMODESTATE --> OKSENDMSGSTATE : handleOneIncomingReply()\nentered NORMALMODE 072 OKSENDMSGSTATE --> WAITMSGREPLYSTATE : send original pended message 073 074 IDLESTATE --> POLLSTATE : transmitLoop()\nno work 075 POLLSTATE --> WAITMSGREPLYSTATE : transmitLoop()\npoll msg exists, send it 076 POLLSTATE --> IDLESTATE : transmitLoop()\nno poll msg to send 077 078 WAITMSGREPLYSTATE --> AUTORETRYSTATE : handleOneIncomingReply()\nwhen tagged as error reply 079 AUTORETRYSTATE --> IDLESTATE : to drive a repeat of a message 080 081NOTIFIEDSTATE : Transmit thread wakes up and processes 082POLLSTATE : Transient while deciding to send poll 083OKSENDMSGSTATE : Transient while deciding to send\noriginal message after mode change 084AUTORETRYSTATE : Transient while deciding to resend auto-retry message 085WAITREPLYINPROGMODESTATE : Sent request to go to programming mode,\nwaiting reply 086WAITREPLYINNORMMODESTATE : Sent request to go to normal mode,\nwaiting reply 087WAITMSGREPLYSTATE : Have sent message, waiting a\nresponse from layout 088 089Note left of AUTORETRYSTATE : This state handles timeout of\nmessages marked for autoretry 090Note left of OKSENDMSGSTATE : Transient internal state\nwill transition when going back\nto send message that\nwas deferred for mode change. 091 092@enduml 093 */ 094 095public abstract class AbstractMRTrafficController { 096 097 private final Runnable shutDownTask = this::terminate; // retain for possible removal. 098 099 /** 100 * Create a new unnamed MRTrafficController. 101 */ 102 public AbstractMRTrafficController() { 103 log.debug("Creating AbstractMRTrafficController instance"); 104 mCurrentMode = NORMALMODE; 105 mCurrentState = IDLESTATE; 106 allowUnexpectedReply = false; 107 108 109 // We use a shutdown task here to make sure the connection is left 110 // in a clean state prior to exiting. This is required on systems 111 // which have a service mode to ensure we don't leave the system 112 // in an unusable state. Once the shutdown task executes, the connection 113 // must be considered permanently closed. 114 115 InstanceManager.getDefault(ShutDownManager.class).register(shutDownTask); 116 } 117 118 private boolean synchronizeRx = true; 119 120 protected void setSynchronizeRx(boolean val) { 121 synchronizeRx = val; 122 } 123 124 protected boolean getSynchronizeRx() { 125 return synchronizeRx; 126 } 127 128 // The methods to implement the abstract Interface 129 130 protected final Vector<AbstractMRListener> cmdListeners = new Vector<>(); 131 132 /** 133 * Add a Listener to the Listener list. 134 * @param l The Listener to be added, not null. 135 */ 136 protected synchronized void addListener(AbstractMRListener l) { 137 // add only if not already registered 138 if (l == null) { 139 throw new NullPointerException(); 140 } 141 if (!cmdListeners.contains(l)) { 142 cmdListeners.addElement(l); 143 } 144 } 145 146 /** 147 * Add a Listener to start of the Listener list. 148 * Intended for use only by system Consoles which may prefer notification 149 * before other objects have processed a Message and sent a Reply. 150 * @param l The Listener to be added, not null. 151 */ 152 protected synchronized void addConsoleListener(@Nonnull AbstractMRListener l){ 153 // add only if not already registered 154 if (!cmdListeners.contains(l)) { 155 cmdListeners.insertElementAt(l, 0); 156 } 157 } 158 159 /** 160 * Remove a Listener from the Listener list. 161 * The Listener will receive no further notifications. 162 * @param l The Listener to be removed. 163 */ 164 protected synchronized void removeListener(AbstractMRListener l) { 165 if (cmdListeners.contains(l)) { 166 cmdListeners.removeElement(l); 167 } 168 } 169 170 /** 171 * Forward a Message to registered listeners. 172 * 173 * @param m Message to be forwarded intact 174 * @param notMe One (optional) listener to be skipped, usually because it's 175 * the originating object. 176 */ 177 @SuppressWarnings("unchecked") 178 protected void notifyMessage(AbstractMRMessage m, AbstractMRListener notMe) { 179 // make a copy of the listener vector to synchronized not needed for transmit 180 Vector<AbstractMRListener> v; 181 synchronized (this) { 182 // FIXME: unnecessary synchronized; the Vector IS already thread-safe. 183 v = (Vector<AbstractMRListener>) cmdListeners.clone(); 184 } 185 // forward to all listeners 186 int cnt = v.size(); 187 for (int i = 0; i < cnt; i++) { 188 AbstractMRListener client = v.elementAt(i); 189 if (notMe != client) { 190 log.debug("notify message, client: {}", client); 191 try { 192 forwardMessage(client, m); 193 } catch (RuntimeException e) { 194 log.warn("notify: During message dispatch to {}", client, e); 195 } 196 } 197 } 198 } 199 200 /** 201 * Implement this to forward a specific message type to a protocol-specific 202 * listener interface. 203 * This puts the casting into the concrete class. 204 * @param client abstract listener. 205 * @param m message to forward. 206 */ 207 protected abstract void forwardMessage(AbstractMRListener client, AbstractMRMessage m); 208 209 /** 210 * Invoked if it's appropriate to do low-priority polling of the command 211 * station, this should return the next message to send, or null if the 212 * TrafficController should just sleep. 213 * @return Formatted poll message 214 */ 215 protected abstract AbstractMRMessage pollMessage(); 216 217 protected abstract AbstractMRListener pollReplyHandler(); 218 219 // Written by the transmit thread when a message is handed to the port, read 220 // by the receive thread to decide who the next reply belongs to. It has to 221 // be volatile: without it the receive thread may keep observing a stale 222 // sender and deliver replies to a listener which is no longer the one 223 // waiting for them. 224 protected volatile AbstractMRListener mLastSender = null; 225 226 protected volatile int mCurrentMode; 227 public static final int NORMALMODE = 1; 228 public static final int PROGRAMINGMODE = 4; 229 230 /** 231 * Set the system to programming mode. 232 * @see #enterNormalMode() 233 * 234 * @return any message that needs to be returned to the Command Station 235 * to change modes. If no message is needed, returns null. 236 */ 237 protected abstract AbstractMRMessage enterProgMode(); 238 239 /** 240 * Sets the system to normal mode during programming while in IDLESTATE. 241 * If {@link #programmerIdle()} returns true, enterNormalMode() is 242 * called after a timeout. 243 * @see #enterProgMode() 244 * 245 * @return any message that needs to be returned to the Command Station 246 * to change modes. If no message is needed, returns null. 247 */ 248 protected abstract AbstractMRMessage enterNormalMode(); 249 250 /** 251 * Check if the programmer is idle. 252 * Override in the system specific code if necessary (see notes for 253 * {@link #enterNormalMode()}. 254 * 255 * @return true if not busy programming 256 */ 257 protected boolean programmerIdle() { 258 return true; 259 } 260 261 /** 262 * Get the delay (wait time) after enabling the programming track. 263 * Override in subclass to add a longer delay. 264 * 265 * @return 0 as default delay 266 */ 267 protected int enterProgModeDelayTime() { 268 return 0; 269 } 270 271 protected volatile int mCurrentState; 272 public static final int IDLESTATE = 10; // nothing happened 273 public static final int NOTIFIEDSTATE = 15; // xmt notified, will next wake 274 public static final int WAITMSGREPLYSTATE = 25; // xmt has sent, await reply to message 275 public static final int WAITREPLYINPROGMODESTATE = 30; // xmt has done mode change, await reply 276 public static final int WAITREPLYINNORMMODESTATE = 35; // xmt has done mode change, await reply 277 public static final int OKSENDMSGSTATE = 40; // mode change reply here, send original msg 278 public static final int AUTORETRYSTATE = 45; // received message where automatic recovery may occur with a retransmission, re-send original msg 279 public static final int POLLSTATE = 50; // Send program mode or poll message 280 281 protected boolean allowUnexpectedReply; 282 283 /** 284 * Set whether the command station may send messages without a request 285 * sent to it. 286 * 287 * @param expected true to allow messages without a prior request 288 */ 289 protected void setAllowUnexpectedReply(boolean expected) { 290 allowUnexpectedReply = expected; 291 } 292 293 /** 294 * Forward a "Reply" from layout to registered listeners. 295 * 296 * @param r Reply to be forwarded intact 297 * @param dest One (optional) listener to be skipped, usually because it's 298 * the originating object. 299 */ 300 @SuppressWarnings("unchecked") 301 protected void notifyReply(AbstractMRReply r, AbstractMRListener dest) { 302 // make a copy of the listener vector to synchronized (not needed for transmit?) 303 Vector<AbstractMRListener> v; 304 synchronized (this) { 305 // FIXME: unnecessary synchronized; the Vector IS already thread-safe. 306 v = (Vector<AbstractMRListener>) cmdListeners.clone(); 307 } 308 // forward to all listeners 309 int cnt = v.size(); 310 for (int i = 0; i < cnt; i++) { 311 AbstractMRListener client = v.elementAt(i); 312 log.debug("notify reply, client: {}", client); 313 try { 314 //skip dest for now, we'll send the message to there last. 315 if (dest != client) { 316 forwardReply(client, r); 317 } 318 } catch (RuntimeException e) { 319 log.warn("notify: During reply dispatch to {}", client, e); 320 } 321 } 322 323 // forward to the last listener who sent a message 324 // this is done _second_ so monitoring can have already stored the reply 325 // before a response is sent 326 if (dest != null) { 327 log.debug("notify reply, dest: {}", dest); 328 forwardReply(dest, r); 329 } 330 } 331 332 protected abstract void forwardReply(AbstractMRListener client, AbstractMRReply m); 333 334 /** 335 * Report whether this traffic controller is at rest: no message waiting for 336 * a reply, and nothing left to send. 337 * <p> 338 * A listener still waiting for a reply while this returns true is waiting 339 * for something which will never arrive, because the exchange it belongs to 340 * has already been closed by another reply. Intended for watchdogs and 341 * diagnostics: the answer is a best effort snapshot taken without locking, 342 * not a state guaranteed to hold beyond the call. 343 * 344 * @return true if nothing is outstanding and nothing is queued 345 */ 346 public boolean isAtRest() { 347 int state = mCurrentState; 348 return (state == IDLESTATE || state == NOTIFIEDSTATE) && msgQueue.isEmpty(); 349 } 350 351 /** 352 * Messages to be transmitted. 353 */ 354 protected LinkedList<AbstractMRMessage> msgQueue = new LinkedList<>(); 355 protected LinkedList<AbstractMRListener> listenerQueue = new LinkedList<>(); 356 357 /** 358 * Forward message to the port. Messages are queued and then the 359 * transmission thread is notified. 360 * @see #forwardToPort(AbstractMRMessage, AbstractMRListener) 361 * 362 * @param m the message to send 363 * @param reply the Listener sending the message, often provided as 'this' 364 */ 365 protected synchronized void sendMessage(AbstractMRMessage m, AbstractMRListener reply) { 366 msgQueue.addLast(m); 367 listenerQueue.addLast(reply); 368 synchronized (xmtRunnable) { 369 if (mCurrentState == IDLESTATE) { 370 mCurrentState = NOTIFIEDSTATE; 371 xmtRunnable.notify(); 372 } 373 } 374 if (m != null) { 375 log.debug("just notified transmit thread with message {}", m); 376 } 377 } 378 379 /** 380 * Permanent loop for the transmit thread. 381 */ 382 protected void transmitLoop() { 383 log.debug("transmitLoop starts in {}", this); 384 385 // loop forever 386 while (!connectionError && !threadStopRequest) { 387 AbstractMRMessage m = null; 388 AbstractMRListener l = null; 389 // check for something to do 390 synchronized (this) { 391 if (!msgQueue.isEmpty()) { 392 // yes, something to do 393 m = msgQueue.getFirst(); 394 msgQueue.removeFirst(); 395 l = listenerQueue.getFirst(); 396 listenerQueue.removeFirst(); 397 mCurrentState = WAITMSGREPLYSTATE; 398 log.debug("transmit loop has something to do: {}", m); 399 } else if (mCurrentState != IDLESTATE) { 400 // Nothing to send. Go IDLE while still holding the lock 401 // that sendMessage() holds while queueing, so that a 402 // message queued from now on is guaranteed to see IDLE and 403 // notify this thread. Setting IDLE after releasing the lock 404 // overwrote the NOTIFIEDSTATE of a message queued in 405 // between, and that message then waited out the whole 406 // poll interval (about 100 ms) before being sent. 407 log.debug("Setting IDLESTATE, current mode {}", mCurrentMode); 408 mCurrentState = IDLESTATE; 409 } // release lock here to proceed in parallel 410 } 411 // if a message has been extracted, process it 412 if (m != null) { 413 // check for need to change mode 414 log.debug("Start msg, state = {}", mCurrentMode); 415 if (m.getNeededMode() != mCurrentMode) { 416 AbstractMRMessage modeMsg; 417 if (m.getNeededMode() == PROGRAMINGMODE) { 418 // change state to programming mode and send message 419 modeMsg = enterProgMode(); 420 if (modeMsg != null) { 421 mCurrentState = WAITREPLYINPROGMODESTATE; 422 log.debug("Enter Programming Mode"); 423 forwardToPort(modeMsg, null); 424 // wait for reply 425 transmitWait(m.getTimeout(), WAITREPLYINPROGMODESTATE, "enter programming mode interrupted"); 426 } 427 } else { 428 // change state to normal and send message 429 modeMsg = enterNormalMode(); 430 if (modeMsg != null) { 431 mCurrentState = WAITREPLYINNORMMODESTATE; 432 log.debug("Enter Normal Mode"); 433 forwardToPort(modeMsg, null); 434 // wait for reply 435 transmitWait(m.getTimeout(), WAITREPLYINNORMMODESTATE, "enter normal mode interrupted"); 436 } 437 } 438 if (modeMsg != null) { 439 checkReplyInDispatch(); 440 if (mCurrentState != OKSENDMSGSTATE) { 441 handleTimeout(modeMsg, l); 442 } 443 mCurrentState = WAITMSGREPLYSTATE; 444 } else { 445 // no mode message required, but the message 446 // needs a different mode 447 log.debug("Setting mode to: {}", m.getNeededMode()); 448 mCurrentMode = m.getNeededMode(); 449 } 450 } 451 forwardToPort(m, l); 452 // reply expected? 453 if (m.replyExpected()) { 454 log.debug("reply expected is true for message {}",m); 455 // wait for a reply, or eventually timeout 456 transmitWait(m.getTimeout(), WAITMSGREPLYSTATE, "transmitLoop interrupted"); 457 checkReplyInDispatch(); 458 if (mCurrentState == WAITMSGREPLYSTATE) { 459 handleTimeout(m, l); 460 } else if (mCurrentState == AUTORETRYSTATE) { 461 log.debug("Message added back to queue: {}", m); 462 msgQueue.addFirst(m); 463 listenerQueue.addFirst(l); 464 synchronized (xmtRunnable) { 465 mCurrentState = IDLESTATE; 466 } 467 } else { 468 resetTimeout(m); 469 } 470 } // just continue to the next message from here 471 } else { 472 // nothing to do, IDLESTATE was set above under the queue lock 473 // wait for something to send 474 if (mWaitBeforePoll > waitTimePoll || mCurrentMode == PROGRAMINGMODE) { 475 try { 476 long startTime = Calendar.getInstance().getTimeInMillis(); 477 synchronized (xmtRunnable) { 478 // Only wait while still IDLE. If a message was 479 // queued since the check above, sendMessage() has 480 // already moved to NOTIFIEDSTATE and sent its 481 // notify(), which nobody was waiting for. 482 if (mCurrentState == IDLESTATE) { 483 xmtRunnable.wait(mWaitBeforePoll); 484 } 485 } 486 long endTime = Calendar.getInstance().getTimeInMillis(); 487 waitTimePoll = waitTimePoll + endTime - startTime; 488 } catch (InterruptedException e) { 489 Thread.currentThread().interrupt(); // retain if needed later 490 // end of transmit loop 491 break; 492 } 493 } 494 // once we decide that mCurrentState is in the IDLESTATE and there's an xmt msg we must guarantee 495 // the change of mCurrentState to one of the waiting for reply states. Therefore we need to synchronize. 496 synchronized (this) { 497 if (mCurrentState != NOTIFIEDSTATE && mCurrentState != IDLESTATE) { 498 log.error("left timeout in unexpected state: {}", mCurrentState); 499 } 500 if (mCurrentState == IDLESTATE) { 501 mCurrentState = POLLSTATE; // this prevents other transitions from the IDLESTATE 502 } 503 } 504 // went around with nothing to do; leave programming state if in it 505 if (mCurrentMode == PROGRAMINGMODE) { 506 log.debug("Timeout - in service mode"); 507 } 508 if (mCurrentState == POLLSTATE && mCurrentMode == PROGRAMINGMODE && programmerIdle()) { 509 log.debug("timeout causes leaving programming mode"); 510 mCurrentState = WAITREPLYINNORMMODESTATE; 511 AbstractMRMessage msg = enterNormalMode(); 512 // if the enterNormalMode() message is null, we 513 // don't want to try to send it to the port. 514 if (msg != null) { 515 forwardToPort(msg, null); 516 // wait for reply 517 transmitWait(msg.getTimeout(), WAITREPLYINNORMMODESTATE, "interrupted while leaving programming mode"); 518 checkReplyInDispatch(); 519 // exit program mode timeout? 520 if (mCurrentState == WAITREPLYINNORMMODESTATE) { 521 // entering normal mode via timeout 522 handleTimeout(msg, l); 523 mCurrentMode = NORMALMODE; 524 } 525 // and go around again 526 } 527 } else if (mCurrentState == POLLSTATE && mCurrentMode == NORMALMODE) { 528 // We may need to poll 529 AbstractMRMessage msg = pollMessage(); 530 if (msg != null) { 531 // yes, send that 532 log.debug("Sending poll, wait time {}", waitTimePoll); 533 mCurrentState = WAITMSGREPLYSTATE; 534 forwardToPort(msg, pollReplyHandler()); 535 // wait for reply 536 log.debug("Still waiting for reply"); 537 transmitWait(msg.getTimeout(), WAITMSGREPLYSTATE, "interrupted while waiting poll reply"); 538 checkReplyInDispatch(); 539 // and go around again 540 if (mCurrentState == WAITMSGREPLYSTATE) { 541 handleTimeout(msg, l); 542 } else { 543 resetTimeout(msg); 544 } 545 } 546 waitTimePoll = 0; 547 } 548 // no messages, so back to idle 549 if (mCurrentState == POLLSTATE) { 550 mCurrentState = IDLESTATE; 551 } 552 } 553 } 554 } // end of transmit loop; go around again 555 556 protected void transmitWait(int waitTime, int state, String interruptMessage) { 557 // wait() can have spurious wakeup! 558 // so we protect by making sure the entire timeout time is used 559 long currentTime = Calendar.getInstance().getTimeInMillis(); 560 long endTime = currentTime + waitTime; 561 while (endTime > (currentTime = Calendar.getInstance().getTimeInMillis())) { 562 long wait = endTime - currentTime; 563 try { 564 synchronized (xmtRunnable) { 565 // Do not wait if the current state has changed since we 566 // last set it. 567 if (mCurrentState != state) { 568 return; 569 } 570 xmtRunnable.wait(wait); // rcvr normally ends this w state change 571 } 572 } catch (InterruptedException e) { 573 Thread.currentThread().interrupt(); // retain if needed later 574 String[] packages = this.getClass().getName().split("\\."); 575 String name = (packages.length>=2 ? packages[packages.length-2]+"." :"") 576 +(packages.length>=1 ? packages[packages.length-1] :""); 577 if (!threadStopRequest) { 578 log.error("{} in transmitWait(..) of {}", interruptMessage, name); 579 } else { 580 log.debug("during shutdown, {} in transmitWait(..) of {}", interruptMessage, name); 581 } 582 } 583 } 584 log.debug("Timeout in transmitWait, mCurrentState: {}", mCurrentState); 585 } 586 587 // Dispatch control and timer 588 protected boolean replyInDispatch = false; // true when reply has been received but dispatch not completed 589 private int maxDispatchTime = 0; 590 private int warningMessageTime = DISPATCH_WARNING_TIME; 591 private static final int DISPATCH_WAIT_INTERVAL = 100; 592 private static final int DISPATCH_WARNING_TIME = 12000; // report warning when max dispatch time exceeded 593 private static final int WARN_NEXT_TIME = 1000; // report every second 594 595 private void checkReplyInDispatch() { 596 int loopCount = 0; 597 while (replyInDispatch) { 598 try { 599 synchronized (xmtRunnable) { 600 xmtRunnable.wait(DISPATCH_WAIT_INTERVAL); 601 } 602 } catch (InterruptedException e) { 603 Thread.currentThread().interrupt(); // retain if needed later 604 if (threadStopRequest) return; // don't log an error if closing. 605 String[] packages = this.getClass().getName().split("\\."); 606 String name = (packages.length>=2 ? packages[packages.length-2]+"." :"") 607 +(packages.length>=1 ? packages[packages.length-1] :""); 608 log.error("transmitLoop interrupted in class {}", name); 609 } 610 loopCount++; 611 int currentDispatchTime = loopCount * DISPATCH_WAIT_INTERVAL; 612 if (currentDispatchTime > maxDispatchTime) { 613 maxDispatchTime = currentDispatchTime; 614 if (currentDispatchTime >= warningMessageTime) { 615 warningMessageTime = warningMessageTime + WARN_NEXT_TIME; 616 log.debug("Max dispatch time is now {}", currentDispatchTime); 617 } 618 } 619 } 620 } 621 622 /** 623 * Determine if the interface is down. 624 * 625 * @return timeoutFlag 626 */ 627 public boolean hasTimeouts() { 628 return timeoutFlag; 629 } 630 631 protected boolean timeoutFlag = false; 632 protected int timeouts = 0; 633 protected boolean flushReceiveChars = false; 634 635 protected void handleTimeout(AbstractMRMessage msg, AbstractMRListener l) { 636 //log.debug("Timeout mCurrentState: {}", mCurrentState); 637 warnOnTimeout(msg, l); 638 timeouts++; 639 timeoutFlag = true; 640 flushReceiveChars = true; 641 } 642 643 protected void warnOnTimeout(AbstractMRMessage msg, AbstractMRListener l) { 644 String[] packages = this.getClass().getName().split("\\."); 645 String name = (packages.length>=2 ? packages[packages.length-2]+"." :"") 646 +(packages.length>=1 ? packages[packages.length-1] :""); 647 648 log.warn("Timeout on reply to message: {} consecutive timeouts = {} in {}", msg, timeouts, name); 649 } 650 651 protected void resetTimeout(AbstractMRMessage msg) { 652 if (timeouts > 0) { 653 log.debug("Reset timeout after {} timeouts", timeouts); 654 } 655 timeouts = 0; 656 timeoutFlag = false; 657 } 658 659 /** 660 * Add header to the outgoing byte stream. 661 * 662 * @param msg the output byte stream 663 * @param m Message results 664 * @return next location in the stream to fill 665 */ 666 protected int addHeaderToOutput(byte[] msg, AbstractMRMessage m) { 667 return 0; 668 } 669 670 protected int mWaitBeforePoll = 100; 671 protected long waitTimePoll = 0; 672 673 /** 674 * Add trailer to the outgoing byte stream. 675 * 676 * @param msg the output byte stream 677 * @param offset the first byte not yet used 678 * @param m output message to extend 679 */ 680 protected void addTrailerToOutput(byte[] msg, int offset, AbstractMRMessage m) { 681 if (!m.isBinary()) { 682 msg[offset] = 0x0d; 683 } 684 } 685 686 /** 687 * Determine how many bytes the entire message will take, including 688 * space for header and trailer. 689 * 690 * @param m the message to be sent 691 * @return number of bytes 692 */ 693 protected int lengthOfByteStream(AbstractMRMessage m) { 694 int len = m.getNumDataElements(); 695 int cr = 0; 696 if (!m.isBinary()) { 697 cr = 1; // space for return char 698 } 699 return len + cr; 700 } 701 702 protected boolean xmtException = false; 703 704 /** 705 * Actually transmit the next message to the port. 706 * @see #sendMessage(AbstractMRMessage, AbstractMRListener) 707 * 708 * @param m the message to send 709 * @param reply the Listener sending the message, often provided as 'this' 710 */ 711 @SuppressFBWarnings(value = {"TLW_TWO_LOCK_WAIT"}, 712 justification = "Two locks needed for synchronization here, this is OK") 713 protected synchronized void forwardToPort(AbstractMRMessage m, AbstractMRListener reply) { 714 log.debug("forwardToPort message: [{}]", m); 715 // remember who sent this 716 mLastSender = reply; 717 718 // forward the message to the registered recipients, 719 // which includes the communications monitor, except the sender. 720 // Schedule notification via the Swing event queue to ensure order 721 log.trace("about to start XmtNotifier for {} last: {}", m, mLastSender, new Exception("traceback")); 722 Runnable r = new XmtNotifier(m, mLastSender, this); 723 SwingUtilities.invokeLater(r); 724 725 // stream to port in single write, as that's needed by serial 726 int byteLength = lengthOfByteStream(m); 727 byte[] msg= new byte[byteLength]; 728 log.debug("copying message, length = {}", byteLength); 729 // add header 730 int offset = addHeaderToOutput(msg, m); 731 732 // add data content 733 int len = m.getNumDataElements(); 734 log.debug("copying data to message, length = {}", len); 735 if (len > byteLength) { // happens somehow 736 log.warn("Invalid message array size {} for {} elements, truncated", byteLength, len); 737 } 738 for (int i = 0; (i < len && i < byteLength); i++) { 739 msg[i + offset] = (byte) m.getElement(i); 740 } 741 // add trailer 742 addTrailerToOutput(msg, len + offset, m); 743 // and stream the bytes 744 try { 745 if (ostream != null) { 746 if (log.isDebugEnabled()) { 747 StringBuilder f = new StringBuilder(); 748 for (int i = 0; i < msg.length; i++) { 749 f.append(String.format("%02X ",0xFF & msg[i])); 750 } 751 log.debug("formatted message: {}", f.toString() ); 752 } 753 while (m.getRetries() >= 0) { 754 if (portReadyToSend(controller)) { 755 ostream.write(msg); 756 ostream.flush(); 757 log.debug("written, msg timeout: {} mSec", m.getTimeout()); 758 break; 759 } else if (m.getRetries() >= 0) { 760 log.debug("Retry message: {} attempts remaining: {}", m, m.getRetries()); 761 m.setRetries(m.getRetries() - 1); 762 try { 763 synchronized (xmtRunnable) { 764 xmtRunnable.wait(m.getTimeout()); 765 } 766 } catch (InterruptedException e) { 767 Thread.currentThread().interrupt(); // retain if needed later 768 log.error("retry wait interrupted"); 769 } 770 } else { 771 log.warn("sendMessage: port not ready for data sending: {}", Arrays.toString(msg)); 772 } 773 } 774 } else { // ostream is null 775 // no stream connected 776 connectionWarn(); 777 } 778 } catch (IOException | RuntimeException e) { 779 // TODO Currently there's no port recovery if an exception occurs 780 // must restart JMRI to clear xmtException. 781 xmtException = true; 782 portWarn(e); 783 } 784 } 785 786 protected void connectionWarn() { 787 log.warn("sendMessage: no connection established for {}", this.getClass().getName(), new Exception()); 788 } 789 790 protected void portWarn(Exception e) { 791 log.warn("sendMessage: Exception: In {} port warn: ", this.getClass().getName(), e); 792 } 793 794 protected boolean connectionError = false; 795 796 protected void portWarnTCP(Exception e) { 797 log.warn("Exception java net: ", e); 798 connectionError = true; 799 } 800 // methods to connect/disconnect to a source of data in an AbstractPortController 801 802 public AbstractPortController controller = null; 803 804 public boolean status() { 805 return (ostream != null && istream != null); 806 } 807 808 protected volatile Thread xmtThread = null; 809 protected volatile Thread rcvThread = null; 810 811 protected volatile Runnable xmtRunnable = null; 812 813 /** 814 * Make connection to an existing PortController object. 815 * 816 * @param p the PortController 817 */ 818 public void connectPort(AbstractPortController p) { 819 rcvException = false; 820 connectionError = false; 821 xmtException = false; 822 threadStopRequest = false; 823 try { 824 istream = p.getInputStream(); 825 ostream = p.getOutputStream(); 826 if (controller != null) { 827 log.warn("connectPort: connect called while connected"); 828 } else { 829 log.debug("connectPort invoked"); 830 } 831 controller = p; 832 // and start threads 833 xmtThread = jmri.util.ThreadingUtil.newThread( 834 xmtRunnable = new Runnable() { 835 @Override 836 public void run() { 837 try { 838 transmitLoop(); 839 } catch (Throwable e) { 840 if (!threadStopRequest) { 841 log.error("Transmit thread terminated prematurely by: {}", e, e); 842 } 843 // see http://docs.oracle.com/javase/7/docs/api/java/lang/ThreadDeath.html 844 // ThreadDeath must be thrown per Java API Javadocs 845 // 846 // The type ThreadDeath has been deprecated since version 20 and marked for removal 847 // and the warning cannot be suppressed in Java 21. But external libraries might 848 // throw the exception outside of JMRI control. So check the name of the exception 849 // instead of using "instanceof". 850 if ("java.lang.ThreadDeath".equals(e.getClass().getName())) { 851 throw e; 852 } 853 } 854 } 855 }); 856 857 String[] packages = this.getClass().getName().split("\\."); 858 xmtThread.setName( 859 (packages.length>=2 ? packages[packages.length-2]+"." :"") 860 +(packages.length>=1 ? packages[packages.length-1] :"") 861 +" Transmit thread"); 862 863 xmtThread.setDaemon(true); 864 xmtThread.setPriority(Thread.MAX_PRIORITY-1); //bump up the priority 865 xmtThread.start(); 866 867 rcvThread = jmri.util.ThreadingUtil.newThread( 868 new Runnable() { 869 @Override 870 public void run() { 871 receiveLoop(); 872 } 873 }); 874 rcvThread.setName( 875 (packages.length>=2 ? packages[packages.length-2]+"." :"") 876 +(packages.length>=1 ? packages[packages.length-1] :"") 877 +" Receive thread"); 878 879 rcvThread.setPriority(Thread.MAX_PRIORITY); //bump up the priority 880 rcvThread.setDaemon(true); 881 rcvThread.start(); 882 883 } catch (RuntimeException e) { 884 log.error("Failed to start up communications. Error was: ", e); 885 log.debug("Full trace:", e); 886 } 887 } 888 889 /** 890 * Get the port name for this connection from the TrafficController. 891 * 892 * @return the name of the port 893 */ 894 public String getPortName() { 895 return controller.getCurrentPortName(); 896 } 897 898 /** 899 * Break connection to existing PortController object. Once broken, attempts 900 * to send via "message" member will fail. 901 * 902 * @param p the PortController 903 */ 904 public void disconnectPort(AbstractPortController p) { 905 istream = null; 906 ostream = null; 907 if (controller != p) { 908 log.warn("disconnectPort: disconnect called from non-connected AbstractPortController"); 909 } 910 controller = null; 911 threadStopRequest = true; 912 } 913 914 /** 915 * Check if PortController object can be sent to. 916 * 917 * @param p the PortController 918 * @return true if ready, false otherwise May throw an Exception. 919 */ 920 public boolean portReadyToSend(AbstractPortController p) { 921 if (p != null && !xmtException && !rcvException) { 922 return true; 923 } else { 924 return false; 925 } 926 } 927 928 // data members to hold the streams 929 protected DataInputStream istream = null; 930 protected OutputStream ostream = null; 931 932 protected boolean rcvException = false; 933 934 protected int maxRcvExceptionCount = 100; 935 936 /** 937 * Handle incoming characters. This is a permanent loop, looking for input 938 * messages in character form on the stream connected to the PortController 939 * via {@link #connectPort(AbstractPortController)}. 940 * <p> 941 * Each turn of the loop is the receipt of a single message. 942 */ 943 public void receiveLoop() { 944 log.debug("receiveLoop starts in {}", this); 945 int errorCount = 0; 946 while (errorCount < maxRcvExceptionCount && !threadStopRequest) { // stream close will exit via exception 947 try { 948 handleOneIncomingReply(); 949 errorCount = 0; 950 } catch (java.io.InterruptedIOException e) { 951 // related to InterruptedException, catch first 952 break; 953 } catch (IOException e) { 954 rcvException = true; 955 reportReceiveLoopException(e); 956 break; 957 } catch (RuntimeException e1) { 958 log.error("Exception in receive loop: {}", e1.toString(), e1); 959 errorCount++; 960 if (errorCount == maxRcvExceptionCount) { 961 rcvException = true; 962 reportReceiveLoopException(e1); 963 } 964 } 965 } 966 if (!threadStopRequest) { // if e.g. unexpected end 967 ConnectionStatus.instance().setConnectionState(controller.getSystemConnectionMemo(), ConnectionStatus.CONNECTION_DOWN); 968 log.debug("Exit from rcv loop in {}", this.getClass()); 969 log.info("Exiting receive loop"); 970 recovery(); // see if you can restart 971 } 972 } 973 974 /** 975 * Disconnect and reset the current PortController. 976 * Invoked at abnormal ending of receiveLoop. 977 */ 978 protected final void recovery() { 979 AbstractPortController adapter = controller; 980 disconnectPort(controller); 981 adapter.recover(); 982 } 983 984 /** 985 * Report an error on the receive loop. Separated so tests can suppress, even 986 * though message is asynchronous. 987 * @param e Exception encountered at lower level to trigger error, or null 988 */ 989 protected void reportReceiveLoopException(Exception e) { 990 log.error("run: Exception: {} in {}", e.toString(), this.getClass().toString(), e); 991 jmri.jmrix.ConnectionStatus.instance().setConnectionState(controller.getSystemConnectionMemo(), jmri.jmrix.ConnectionStatus.CONNECTION_DOWN); 992 if (controller instanceof AbstractNetworkPortController) { 993 portWarnTCP(e); 994 } 995 } 996 997 protected abstract AbstractMRReply newReply(); 998 999 protected abstract boolean endOfMessage(AbstractMRReply r); 1000 1001 /** 1002 * Dummy routine, to be filled by protocols that have to skip some 1003 * start-of-message characters. 1004 * @param istream input source 1005 * @throws IOException from underlying operations 1006 */ 1007 protected void waitForStartOfReply(DataInputStream istream) throws IOException { 1008 } 1009 1010 /** 1011 * Read a single byte, protecting against various timeouts, etc. 1012 * <p> 1013 * When a port is set to have a receive timeout, some will return 1014 * zero bytes, an EOFException or a InterruptedIOException at the end of the timeout. 1015 * In that case, the read() 1016 * should be repeated to get the next real character. 1017 * 1018 * @param istream stream to read 1019 * @return the byte read 1020 * @throws java.io.IOException if unable to read 1021 */ 1022 protected byte readByteProtected(DataInputStream istream) throws IOException { 1023 if (istream == null) { 1024 throw new IOException("Input Stream NULL when reading"); 1025 } 1026 while (true) { // loop will repeat until character found 1027 int nchars; 1028 // The istream should be configured so that the following 1029 // read(..) call only blocks for a short time, e.g. 100msec, if no 1030 // data is available. It's OK if it 1031 // throws e.g. java.io.InterruptedIOException 1032 // in that case, as the calling loop should just go around 1033 // and request input again. This semi-blocking behavior will 1034 // let the terminateThreads() method end this thread cleanly. 1035 nchars = istream.read(rcvBuffer, 0, 1); 1036 if (nchars == -1) { 1037 // No more bytes can be read from the channel 1038 throw new IOException("Connection not terminated normally"); 1039 } 1040 if (nchars > 0) { 1041 return rcvBuffer[0]; 1042 } 1043 } 1044 } 1045 1046 // Defined this way to reduce new object creation 1047 private byte[] rcvBuffer = new byte[1]; 1048 1049 /** 1050 * Get characters from the input source, and file a message. 1051 * <p> 1052 * Returns only when the message is complete. 1053 * <p> 1054 * Only used in the Receive thread. 1055 * <p> 1056 * Handles timeouts on read by ignoring zero-length reads. 1057 * 1058 * @param msg message to fill 1059 * @param istream character source. 1060 * @throws IOException when presented by the input source. 1061 */ 1062 protected void loadChars(AbstractMRReply msg, DataInputStream istream) 1063 throws IOException { 1064 int i; 1065 for (i = 0; i < msg.maxSize(); i++) { 1066 byte char1 = readByteProtected(istream); 1067 log.trace("char: {} i: {}",(char1&0xFF),i); 1068 // if there was a timeout, flush any char received and start over 1069 if (flushReceiveChars) { 1070 log.warn("timeout flushes receive buffer: {}", msg); 1071 msg.flush(); 1072 i = 0; // restart 1073 flushReceiveChars = false; 1074 } 1075 if (canReceive()) { 1076 msg.setElement(i, char1); 1077 if (endOfMessage(msg)) { 1078 break; 1079 } 1080 } else { 1081 i--; // flush char 1082 log.error("unsolicited character received: {}", Integer.toHexString(char1)); 1083 } 1084 } 1085 } 1086 1087 /** 1088 * Override in the system specific code if necessary 1089 * 1090 * @return true if it is okay to buffer receive characters into a reply 1091 * message. When false, discard char received 1092 */ 1093 protected boolean canReceive() { 1094 return true; 1095 } 1096 1097 private int retransmitCount = 0; 1098 1099 /** 1100 * Executes a reply distribution action on the appropriate thread for JMRI. 1101 * @param r a runnable typically encapsulating a MRReply and the iteration code needed to 1102 * send it to all the listeners. 1103 */ 1104 protected void distributeReply(Runnable r) { 1105 try { 1106 if (synchronizeRx) { 1107 SwingUtilities.invokeAndWait(r); 1108 } else { 1109 SwingUtilities.invokeLater(r); 1110 } 1111 } catch (InterruptedException ie) { 1112 if (threadStopRequest) return; 1113 log.error("Unexpected exception in invokeAndWait", ie); 1114 } catch (java.lang.reflect.InvocationTargetException| RuntimeException e) { 1115 // For InvocationTargetException, log the wrapped cause (the 1116 // real failure), not the reflection wrapper itself. 1117 Throwable toLog = (e instanceof java.lang.reflect.InvocationTargetException && e.getCause() != null) 1118 ? e.getCause() : e; 1119 log.error("Unexpected exception in invokeAndWait", toLog); 1120 return; 1121 } 1122 log.debug("dispatch thread invoked"); 1123 } 1124 1125 /** 1126 * Handle each reply when complete. 1127 * <p> 1128 * (This is public for testing purposes) Runs in the "Receive" thread. 1129 * 1130 * @throws java.io.IOException on error. 1131 */ 1132 public void handleOneIncomingReply() throws IOException { 1133 // we sit in this until the message is complete, relying on 1134 // threading to let other stuff happen 1135 1136 // Create message off the right concrete class 1137 AbstractMRReply msg = newReply(); 1138 1139 // wait for start if needed 1140 waitForStartOfReply(istream); 1141 1142 // message exists, now fill it 1143 loadChars(msg, istream); 1144 1145 if (threadStopRequest) return; 1146 1147 // message is complete, dispatch it !! 1148 replyInDispatch = true; 1149 log.debug("dispatch reply of length {} contains \"{}\", state {}", msg.getNumDataElements(), msg, mCurrentState); 1150 1151 // forward the message to the registered recipients, 1152 // which includes the communications monitor 1153 // return a notification via the Swing event queue to ensure proper thread 1154 Runnable r = new RcvNotifier(msg, mLastSender, this); 1155 distributeReply(r); 1156 1157 if (!msg.isUnsolicited()) { 1158 // effect on transmit: 1159 switch (mCurrentState) { 1160 case WAITMSGREPLYSTATE: { 1161 // check to see if the response was an error message we want 1162 // to automatically handle by re-queueing the last sent 1163 // message, otherwise go on to the next message 1164 if (msg.isRetransmittableErrorMsg()) { 1165 log.error("Automatic Recovery from Error Message: {}. Retransmitted {} times.", msg, retransmitCount); 1166 synchronized (xmtRunnable) { 1167 mCurrentState = AUTORETRYSTATE; 1168 if (retransmitCount > 0) { 1169 try { 1170 xmtRunnable.wait(retransmitCount * 100L); 1171 } catch (InterruptedException e) { 1172 Thread.currentThread().interrupt(); // retain if needed later 1173 } 1174 } 1175 replyInDispatch = false; 1176 xmtRunnable.notify(); 1177 retransmitCount++; 1178 } 1179 } else { 1180 // update state, and notify to continue 1181 synchronized (xmtRunnable) { 1182 mCurrentState = NOTIFIEDSTATE; 1183 replyInDispatch = false; 1184 xmtRunnable.notify(); 1185 retransmitCount = 0; 1186 } 1187 } 1188 break; 1189 } 1190 case WAITREPLYINPROGMODESTATE: { 1191 // entering programming mode 1192 mCurrentMode = PROGRAMINGMODE; 1193 replyInDispatch = false; 1194 1195 // check to see if we need to delay to allow decoders to become 1196 // responsive 1197 int warmUpDelay = enterProgModeDelayTime(); 1198 if (warmUpDelay != 0) { 1199 try { 1200 synchronized (xmtRunnable) { 1201 xmtRunnable.wait(warmUpDelay); 1202 } 1203 } catch (InterruptedException e) { 1204 Thread.currentThread().interrupt(); // retain if needed later 1205 } 1206 } 1207 // update state, and notify to continue 1208 synchronized (xmtRunnable) { 1209 mCurrentState = OKSENDMSGSTATE; 1210 xmtRunnable.notify(); 1211 } 1212 break; 1213 } 1214 case WAITREPLYINNORMMODESTATE: { 1215 // entering normal mode 1216 mCurrentMode = NORMALMODE; 1217 replyInDispatch = false; 1218 // update state, and notify to continue 1219 synchronized (xmtRunnable) { 1220 mCurrentState = OKSENDMSGSTATE; 1221 xmtRunnable.notify(); 1222 } 1223 break; 1224 } 1225 default: { 1226 replyInDispatch = false; 1227 if (allowUnexpectedReply) { 1228 log.debug("Allowed unexpected reply received in state: {} was {}", mCurrentState, msg); 1229 synchronized (xmtRunnable) { 1230 // The transmit thread sometimes gets stuck 1231 // when unexpected replies are received. Notify 1232 // it to clear the block without a timeout. 1233 // (do not change the current state) 1234 //if(mCurrentState!=IDLESTATE) 1235 xmtRunnable.notify(); 1236 } 1237 } else { 1238 unexpectedReplyStateError(mCurrentState, msg.toString()); 1239 } 1240 } 1241 } 1242 // Unsolicited message 1243 } else { 1244 log.debug("Unsolicited Message Received {}", msg); 1245 1246 replyInDispatch = false; 1247 } 1248 } 1249 1250 /** 1251 * Log an error message for a message received in an unexpected state. 1252 * @param State message state. 1253 * @param msgString message string. 1254 */ 1255 protected void unexpectedReplyStateError(int State, String msgString) { 1256 String[] packages = this.getClass().getName().split("\\."); 1257 String name = (packages.length>=2 ? packages[packages.length-2]+"." :"") 1258 +(packages.length>=1 ? packages[packages.length-1] :""); 1259 log.error("reply complete in unexpected state: {} was {} in class {}", State, msgString, name); 1260 } 1261 1262 /** 1263 * for testing purposes, let us be able to find out 1264 * what the last sender was. 1265 * @return last sender, mLastSender. 1266 */ 1267 public AbstractMRListener getLastSender() { 1268 return mLastSender; 1269 } 1270 1271 protected void terminate() { 1272 log.debug("Cleanup Starts"); 1273 if (ostream == null) { 1274 return; // no connection established 1275 } 1276 AbstractMRMessage modeMsg = enterNormalMode(); 1277 if (modeMsg != null) { 1278 modeMsg.setRetries(100); // set the number of retries 1279 // high, just in case the interface 1280 // is busy when we try to send 1281 forwardToPort(modeMsg, null); 1282 // wait for reply 1283 try { 1284 if (xmtRunnable != null) { 1285 synchronized (xmtRunnable) { 1286 xmtRunnable.wait(modeMsg.getTimeout()); 1287 } 1288 } 1289 } catch (InterruptedException e) { 1290 Thread.currentThread().interrupt(); // retain if needed later 1291 log.error("transmit interrupted"); 1292 } 1293 } 1294 } 1295 1296 /** 1297 * Internal class to remember the Reply object and destination listener with 1298 * a reply is received. 1299 */ 1300 protected static class RcvNotifier implements Runnable { 1301 1302 AbstractMRReply mMsg; 1303 AbstractMRListener mDest; 1304 AbstractMRTrafficController mTc; 1305 1306 public RcvNotifier(AbstractMRReply pMsg, AbstractMRListener pDest, 1307 AbstractMRTrafficController pTc) { 1308 mMsg = pMsg; 1309 mDest = pDest; 1310 mTc = pTc; 1311 } 1312 1313 @Override 1314 public void run() { 1315 log.debug("Delayed rcv notify starts"); 1316 mTc.notifyReply(mMsg, mDest); 1317 } 1318 } // end RcvNotifier 1319 1320 // allow creation of object outside package 1321 protected RcvNotifier newRcvNotifier(AbstractMRReply pMsg, AbstractMRListener pDest, 1322 AbstractMRTrafficController pTc) { 1323 return new RcvNotifier(pMsg, pDest, pTc); 1324 } 1325 1326 /** 1327 * Internal class to remember the Message object and destination listener 1328 * when a message is queued for notification. 1329 */ 1330 protected static class XmtNotifier implements Runnable { 1331 1332 AbstractMRMessage mMsg; 1333 AbstractMRListener mDest; 1334 AbstractMRTrafficController mTc; 1335 1336 public XmtNotifier(AbstractMRMessage pMsg, AbstractMRListener pDest, 1337 AbstractMRTrafficController pTc) { 1338 mMsg = pMsg; 1339 mDest = pDest; 1340 mTc = pTc; 1341 } 1342 1343 @Override 1344 public void run() { 1345 log.debug("Delayed xmt notify starts"); 1346 mTc.notifyMessage(mMsg, mDest); 1347 } 1348 } // end XmtNotifier 1349 1350 /** 1351 * Terminate the receive and transmit threads. 1352 * <p> 1353 * This is intended to be used only by testing subclasses. 1354 */ 1355 public void terminateThreads() { 1356 threadStopRequest = true; 1357 if (xmtThread != null) { 1358 xmtThread.interrupt(); 1359 try { 1360 xmtThread.join(150); 1361 } catch (InterruptedException ie){ 1362 // interrupted during cleanup. 1363 } 1364 } 1365 1366 if (rcvThread != null) { 1367 rcvThread.interrupt(); 1368 try { 1369 rcvThread.join(150); 1370 } catch (InterruptedException ie){ 1371 // interrupted during cleanup. 1372 } 1373 } 1374 // we also need to remove the shutdown task. 1375 InstanceManager.getDefault(ShutDownManager.class).deregister(shutDownTask); 1376 } 1377 1378 /** 1379 * Flag that threads should terminate as soon as they can. 1380 */ 1381 protected volatile boolean threadStopRequest = false; 1382 1383 private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(AbstractMRTrafficController.class); 1384 1385}