Chat is now disconnectable and reconnectable without closing. Made some changes in the thread management to make that possible

This commit is contained in:
Marc Froehlich
2024-02-01 22:35:06 +01:00
parent 7bce7be2ba
commit bd687dc50f
10 changed files with 248 additions and 105 deletions

View File

@@ -5,4 +5,11 @@ public class ApplicationConstants {
* Name of the Application.
*/
public static final String APPLICATION_NAME = "praktiKST";
public static final String DISCSTRING_DISCONNECT_AND_CLOSE = "CLOSEALL";
public static final String DISCSTRING_DISCONNECT_DUE_PAWWORDERROR = "JUSTDSICCAUSEPWWRONG";
public static final String DISCSTRING_DISCONNECTONLY = "ONLYDISCONNECT";
public static final String DISCONNECT_RDR_POISONPILL = "POISONPILL_KILLTHREAD"; //whereever a (blocking) udp or tcp reader in an infinite loop gets this message, it will break this loop
}

View File

@@ -42,7 +42,9 @@ public class AirScoutPeriodicalAPReflectionInquirerTask extends TimerTask {
String asWatchListStringSuffix = asWatchListString;
String host = "255.255.255.255";
int port = 9872;
// int port = 9872;
int port = client.getChatPreferences().getAirScout_asCommunicationPort();
// System.out.println("<<<<<<<<<<<<<<<<<<<<ASPERI: " + port);
// byte[] message = "ASSETPATH: \"KST\" \"AS\" 1440000,DO5AMF,JN49GL,OK1MZM,JN89IW ".getBytes(); Original, ging
InetAddress address;

View File

@@ -13,6 +13,7 @@ import java.util.concurrent.LinkedBlockingQueue;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import kst4contest.ApplicationConstants;
import kst4contest.model.ChatCategory;
import kst4contest.model.ChatMember;
import kst4contest.model.ChatMessage;
@@ -92,7 +93,8 @@ public class ChatController {
/**
* Handles the disconnect of either the chat (Case DISCONNECTONLY) or the
* complete application life including all threads (case CLOSEALL)
* complete application life including all threads (case CLOSEALL)<br/><br/>
* Look in ApplicationConstants for the DISCSTRINGS
*
* @param action: "CLOSEALL" or "DISCONNECTONLYCHAT", on application close event
* (Settings Window closed), Disconnect on Disconnect-Button
@@ -102,7 +104,56 @@ public class ChatController {
this.setDisconnectionPerformedByUser(true);
if (action.equals("CLOSEALL")) {
try {
/**
* Kill UCX packetreader by sending poison pill to the reader thread
*/
DatagramSocket dsocket;
String host = "255.255.255.255";
int port = chatPreferences.getLogsynch_ucxUDPWkdCallListenerPort();
InetAddress address;
address = InetAddress.getByName("255.255.255.255");
DatagramPacket packet = new DatagramPacket(ApplicationConstants.DISCONNECT_RDR_POISONPILL.getBytes(), ApplicationConstants.DISCONNECT_RDR_POISONPILL.length(), address, port);
dsocket = new DatagramSocket();
dsocket.setBroadcast(true);
dsocket.send(packet);
// dsocket.send(packet);
dsocket.close();
readUDPbyUCXThread.interrupt();
} catch (Exception error) {
System.out.println("Chatcrontroller, ERROR: unable to send poison pill to ucxThread");
}
try {
/**
* Kill AS packetreader by sending poison pill to the reader thread
*/
DatagramSocket dsocket;
String host = "255.255.255.255";
int port = chatPreferences.getAirScout_asCommunicationPort();
InetAddress address;
address = InetAddress.getByName("255.255.255.255");
DatagramPacket packet = new DatagramPacket(ApplicationConstants.DISCONNECT_RDR_POISONPILL.getBytes(), ApplicationConstants.DISCONNECT_RDR_POISONPILL.length(), address, port);
dsocket = new DatagramSocket();
dsocket.setBroadcast(true);
dsocket.send(packet);
dsocket.close();
} catch (Exception error) {
System.out.println("Chatcrontroller, ERROR: unable to send poison pill to ucxThread");
}
if (action.equals(ApplicationConstants.DISCSTRING_DISCONNECT_AND_CLOSE)) {
this.lst_chatMemberList.clear();;
this.lst_clusterMemberList.clear();
this.setDisconnected(true);
this.setConnectedAndLoggedIn(false);
this.setConnectedAndNOTLoggedIn(false);
@@ -120,9 +171,8 @@ public class ChatController {
messageRXBus.add(killThreadPoisonPillMsg); //kills messageprocessor
messageTXBus.add(killThreadPoisonPillMsg); //kills writethread
writeThread.interrupt();
readThread.interrupt();
// writeThread.interrupt();
// readThread.interrupt();
beaconTimer.purge();
beaconTimer.cancel();
@@ -160,27 +210,30 @@ public class ChatController {
// TODO Auto-generated catch block
e2.printStackTrace();
}
} else if (action.equals("JUSTDSICCAUSEPWWRONG")){
} else if (action.equals(ApplicationConstants.DISCSTRING_DISCONNECTONLY)){
this.lst_chatMemberList.clear();;
this.lst_clusterMemberList.clear();
this.setDisconnected(true);
this.setConnectedAndLoggedIn(false);
this.setConnectedAndNOTLoggedIn(true);
this.setConnectedAndNOTLoggedIn(false);
// disconnect telnet and kill all sockets and connections
keepAliveTimer.cancel();
keepAliveTimer.purge();
ChatMessage killThreadPoisonPillMsg = new ChatMessage();
killThreadPoisonPillMsg.setMessageText("POISONPILL_KILLTHREAD");
killThreadPoisonPillMsg.setMessageSenderName("POISONPILL_KILLTHREAD");
messageRXBus.clear();
messageTXBus.clear();
messageRXBus.add(killThreadPoisonPillMsg); //kills messageprocessor
messageTXBus.add(killThreadPoisonPillMsg); //kills writethread
writeThread.interrupt();
readThread.interrupt();
beaconTimer.purge();
@@ -193,24 +246,34 @@ public class ChatController {
userActualizationtimer.purge();
userActualizationtimer.cancel();
userActualizationtimer.purge();
userActualizationtimer.cancel();
// consoleReader.interrupt();
messageProcessor.interrupt();
readUDPbyUCXThread.interrupt();
airScoutUDPReaderThread.interrupt();
dbHandler.closeDBConnection();
// messageProcessor.interrupt();
readUDPbyUCXThread.interrupt(); //need poisonpill?
airScoutUDPReaderThread.interrupt(); //need poisonpill?
// dbHandler.closeDBConnection();
// this.dbHandler = null;
try {
if (socket != null) {
socket.close();
}
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (Exception e2) {
// TODO Auto-generated catch block
e2.printStackTrace();
}
}
}
// private String userName = "DO5AMF";
// private String password = "uxskezcj";
private String userName;
private String password;
private String showedName;
@@ -219,8 +282,8 @@ public class ChatController {
private String chatState;
private String hostname = "109.90.0.130";
private String praktiKSTVersion = "wtKST 3.1.4.6";
// private String praktiKSTVersion = "praktiKST 1.0";
// private String praktiKSTVersion = "wtKST 3.1.4.6";
private String praktiKSTVersion = "praktiKST 0.9b";
private String praktiKSTVersionInfo = "2022-10 - 2022-12\ndeveloped by DO5AMF, Marc\nContact: praktimarc@gmail.com\nDonations via paypal are welcome";
private int port = 23001; // kst4contest.test 4 23001
@@ -560,6 +623,8 @@ public class ChatController {
// getLst_toAllMessageList().add(Test);
try {
setDisconnectionPerformedByUser(false);
dbHandler = new DBController();
messageRXBus = new LinkedBlockingQueue<ChatMessage>();
@@ -580,7 +645,7 @@ public class ChatController {
writeThread.setName("Writethread-telnetwriter");
writeThread.start();
readUDPbyUCXThread = new ReadUDPbyUCXMessageThread(12060, this);
readUDPbyUCXThread = new ReadUDPbyUCXMessageThread(chatPreferences.getLogsynch_ucxUDPWkdCallListenerPort(), this);
readUDPbyUCXThread.setName("readUDPbyUCXThread");
readUDPbyUCXThread.start();
@@ -588,7 +653,7 @@ public class ChatController {
messageProcessor.setName("messagebusManagementThread");
messageProcessor.start();
airScoutUDPReaderThread = new ReadUDPbyAirScoutMessageThread(9872, this, "AS", "KST");
airScoutUDPReaderThread = new ReadUDPbyAirScoutMessageThread(chatPreferences.getAirScout_asCommunicationPort(), this, "AS", "KST");
airScoutUDPReaderThread.setName("airscoutudpreaderThread");
airScoutUDPReaderThread.start();
@@ -639,7 +704,7 @@ public class ChatController {
@Override
public void run() {
// System.out.println("[Chatcontroller, info: ] periodical socketcheck");
System.out.println("[Chatcontroller, info: ] periodical socketcheck");
Thread.currentThread().setName("SocketcheckTimer");
@@ -652,13 +717,6 @@ public class ChatController {
chatController.setConnectedAndLoggedIn(false);
chatController.getLst_chatMemberList().clear();
// messageProcessor.interrupt();
// chatController.getReadThread().interrupt();
// chatController.getWriteThread().interrupt();
// keepAliveTimer.wait();
// chatController.getstat
System.out.println("[Chatcontroller, Warning: ] Socket closed or disconnected");
ChatMessage killThreadPoisonPillMsg = new ChatMessage();

View File

@@ -63,6 +63,9 @@ public class DBController {
}
private void initDBConnection() {
System.out.println("DBH: initiate new db connection");
try {
ApplicationFileUtils.copyResourceIfRequired(
ApplicationConstants.APPLICATION_NAME,

View File

@@ -992,9 +992,7 @@ public class MessageBusManagementThread extends Thread {
String messageLine;
while (true) {
try {
messageTextRaw = client.getMessageRXBus().take();

View File

@@ -91,48 +91,7 @@ public class ReadThread extends Thread {
// TODO Auto-generated catch block
e.printStackTrace();
}
// try {
// sleep(3000);
// } catch (InterruptedException e) {
// // TODO Auto-generated catch block
// e.printStackTrace();
// }
// try {
// System.out.println("RDTH: try new socket");
// this.client.getSocket().close();
// this.client.getSocket().close();
// this.client.setSocket(new Socket(this.client.getHostname(), this.client.getPort()));
// socket.connect(new InetSocketAddress(this.client.getHostname(), this.client.getPort()));
// System.out.println("[Readthread, Warning:] new socket connected? -> " + socket.isConnected());
// input = socket.getInputStream();
// reader = new BufferedReader(new InputStreamReader(input));
//
// this.sleep(5000);
// } catch (IOException | InterruptedException e2) {
// // TODO Auto-generated catch block
// System.out.println("fucktah");
// e2.printStackTrace();
// }
// try {
// sleep(2000);
// } catch (InterruptedException e1) {
// // TODO Auto-generated catch block
// e1.printStackTrace();
// }
// try {
// this.client.getSocket().close();
// this.client.setSocket(new Socket(this.client.getHostname(), this.client.getPort()));
// } catch (UnknownHostException e) {
// // TODO Auto-generated catch block
// e.printStackTrace();
// } catch (IOException e) {
// // TODO Auto-generated catch block
// e.printStackTrace();
// }
}
}

View File

@@ -6,6 +6,7 @@ import java.util.Comparator;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import kst4contest.ApplicationConstants;
import kst4contest.model.AirPlane;
import kst4contest.model.AirPlaneReflectionInfo;
import kst4contest.model.ChatMember;
@@ -30,6 +31,8 @@ public class ReadUDPbyAirScoutMessageThread extends Thread {
public ReadUDPbyAirScoutMessageThread(int localPort, ChatController client, String ASIdentificator,
String ChatClientIdentificator) {
this.localPort = localPort;
this.client = client;
this.ASIdentificator = ASIdentificator;
@@ -38,6 +41,7 @@ public class ReadUDPbyAirScoutMessageThread extends Thread {
@Override
public void interrupt() {
System.out.println("ReadUDP");
super.interrupt();
try {
if (this.socket != null) {
@@ -89,6 +93,12 @@ public class ReadUDPbyAirScoutMessageThread extends Thread {
}
socket.receive(packet);
} catch (SocketTimeoutException e2) {
// this will catch the repeating Sockettimeoutexception...nothing to do
// e2.printStackTrace();
@@ -104,6 +114,12 @@ public class ReadUDPbyAirScoutMessageThread extends Thread {
String received = new String(packet.getData(), packet.getOffset(), packet.getLength());
received = received.trim();
if (received.contains(ApplicationConstants.DISCONNECT_RDR_POISONPILL)) {
System.out.println("ReadUdpByASMsgTh, Info: got poison, now dieing....");
break;
}
if (received.contains("ASSETPATH") || received.contains("ASWATCHLIST")) {
// do nothing, that is your own message
} else if (received.contains("ASNEAREST:")) { //answer by airscout

View File

@@ -9,6 +9,7 @@ import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import kst4contest.ApplicationConstants;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
@@ -43,7 +44,7 @@ public class ReadUDPbyUCXMessageThread extends Thread {
super.interrupt();
try {
if (this.socket != null) {
System.out.println(">>>>>>>>>>>>>>ReadUdpbyUCS: closing socket");
this.socket.close();
}
} catch (Exception e) {
@@ -54,6 +55,8 @@ public class ReadUDPbyUCXMessageThread extends Thread {
public void run() {
Thread.currentThread().setName("ReadUDPByUCXLogThread");
DatagramSocket socket = null;
@@ -63,7 +66,7 @@ public class ReadUDPbyUCXMessageThread extends Thread {
try {
socket = new DatagramSocket(12060);
socket.setSoTimeout(11000); //TODO try for end properly
socket.setSoTimeout(2000); //TODO try for end properly
}
catch (SocketException e) {
@@ -75,22 +78,32 @@ public class ReadUDPbyUCXMessageThread extends Thread {
boolean timeOutIndicator = false;
if (this.client.isDisconnectionPerformedByUser()) {
break;//TODO: what if it´s not the finally closage but a band channel change?
}
// packet = new DatagramPacket(buf, buf.length); //TODO: Changed that due to memory leak, check if all works (seems like that)
// DatagramPacket packet = new DatagramPacket(SRPDefinitions.BYTE_BUFFER_MAX_LENGTH); //TODO: Changed that due to memory leak, check if all works (seems like that)
try {
socket.receive(packet);
} catch (SocketTimeoutException e2) {
timeOutIndicator = true;
// this will catch the repeating Sockettimeoutexception...nothing to do
// e2.printStackTrace();
}
}
catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (NullPointerException nE) {
// TODO Auto-generated catch block
nE.printStackTrace();
System.out.println("ReadUdpByUCXTH: Socket not ready");
try {
socket = new DatagramSocket(12060);
socket.setSoTimeout(2000);
} catch (SocketException e) {
throw new RuntimeException(e);
}
}
InetAddress address = packet.getAddress();
@@ -99,9 +112,16 @@ public class ReadUDPbyUCXMessageThread extends Thread {
String received = new String(packet.getData(), packet.getOffset(), packet.getLength());
received = received.trim();
// System.out.println("recvudpucx");
if (this.client.isDisconnectionPerformedByUser()) {
break;//TODO: what if it´s not the finally closage but a band channel change?
}
if (received.contains(ApplicationConstants.DISCONNECT_RDR_POISONPILL)) {
System.out.println("ReadUdpByUCX, Info: got poison, now dieing....");
timeOutIndicator = true;
break;
}
if (!timeOutIndicator) {
processUCXUDPMessage(received);
} else {

View File

@@ -2,13 +2,12 @@ package kst4contest.view;
import java.io.File;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.nio.file.Path;
import java.util.*;
import javafx.scene.media.Media;
import javafx.scene.media.MediaPlayer;
import kst4contest.ApplicationConstants;
import kst4contest.controller.ChatController;
import kst4contest.controller.Utils4KST;
import javafx.application.Application;
@@ -72,7 +71,6 @@ import kst4contest.model.ChatCategory;
import kst4contest.model.ChatMember;
import kst4contest.model.ChatMessage;
import kst4contest.model.ClusterMessage;
import kst4contest.utils.PlayAudioUtils;
public class Kst4ContestApplication extends Application {
@@ -444,7 +442,7 @@ public class Kst4ContestApplication extends Application {
try {
// tbl_chatMemberTable.sort();
tbl_chatMemberTable.sort();
} catch (Exception e) {
System.out.println("[Main.java, Warning:] Table sorting (actualizing) failed this time.");
@@ -454,7 +452,7 @@ public class Kst4ContestApplication extends Application {
});
}
}, new Date(), 3000);
}, new Date(), 10000);
tbl_chatMemberTable.setColumnResizePolicy(TableView.UNCONSTRAINED_RESIZE_POLICY);
tbl_chatMemberTable.autosize();
@@ -1606,8 +1604,21 @@ public class Kst4ContestApplication extends Application {
Menu fileMenu = new Menu("File");
// create menuitems
MenuItem m1 = new MenuItem("Disconnect");
m1.setDisable(true);
menuItemFileDisconnect = new MenuItem("Disconnect");
menuItemFileDisconnect.setDisable(true);
if (chatcontroller.isConnectedAndLoggedIn() || chatcontroller.isConnectedAndNOTLoggedIn()) {
menuItemFileDisconnect.setDisable(false);
} if (chatcontroller.isDisconnected()) {
menuItemFileDisconnect.setDisable(true);
}
menuItemFileDisconnect.setOnAction(new EventHandler<ActionEvent>() {
public void handle(ActionEvent event) {
chatcontroller.disconnect(ApplicationConstants.DISCSTRING_DISCONNECTONLY);
menuItemFileDisconnect.setDisable(true);
}
});
MenuItem m10 = new MenuItem("Exit + disconnect");
m10.setOnAction(new EventHandler<ActionEvent>() {
@@ -1617,7 +1628,7 @@ public class Kst4ContestApplication extends Application {
});
// add menu items to menu
fileMenu.getItems().add(m1);
fileMenu.getItems().add(menuItemFileDisconnect);
fileMenu.getItems().add(m10);
Menu optionsMenu = new Menu("Options");
@@ -1771,6 +1782,7 @@ public class Kst4ContestApplication extends Application {
}
MenuItem menuItemFileDisconnect;
TextField txt_chatMessageUserInput = new TextField();
TextField txt_ownqrg = new TextField();
TextField txt_myQTF = new TextField();
@@ -3113,8 +3125,8 @@ public class Kst4ContestApplication extends Application {
// CheckBox chkBxEnableTRXMsgbyUCX = new CheckBox();
Label lblNotifyEnableSimpleSounds = new Label("Enable audio notifications at: startup, new personal messages, other");
Label lblNotifyCWSounds = new Label("Enable CW callsign spelling for new personal messages");
Label lblNotifyVoiceSounds = new Label("Enable phonetic callsign spelling for new personal messages");
Label lblNotifyEnableCWSounds = new Label("Enable CW callsign spelling for new personal messages");
Label lblNotifyEnableVoiceSounds = new Label("Enable phonetic callsign spelling for new personal messages");
CheckBox chkBxEnableNotifySimpleSounds = new CheckBox();
@@ -3131,9 +3143,42 @@ public class Kst4ContestApplication extends Application {
});
CheckBox chkBxEnableNotifyCWSounds = new CheckBox();
chkBxEnableNotifyCWSounds.setSelected(this.chatcontroller.getChatPreferences().isNotify_playCWCallsignsOnRxedPMs());
chkBxEnableNotifyCWSounds.selectedProperty().addListener(new ChangeListener<Boolean>() {
@Override
public void changed(ObservableValue<? extends Boolean> observable, Boolean oldValue, Boolean newValue) {
chatcontroller.getChatPreferences()
.setNotify_playCWCallsignsOnRxedPMs(chkBxEnableNotifyCWSounds.isSelected());
System.out.println("[Main.java, Info]: Notification CW Callsigns enabled: " + newValue);
}
});
CheckBox chkBxEnableNotifyVoiceSounds = new CheckBox();
chkBxEnableNotifyVoiceSounds.setSelected(this.chatcontroller.getChatPreferences().isNotify_playVoiceCallsignsOnRxedPMs());
chkBxEnableNotifyVoiceSounds.selectedProperty().addListener(new ChangeListener<Boolean>() {
@Override
public void changed(ObservableValue<? extends Boolean> observable, Boolean oldValue, Boolean newValue) {
chatcontroller.getChatPreferences()
.setNotify_playVoiceCallsignsOnRxedPMs(chkBxEnableNotifyVoiceSounds.isSelected());
System.out.println("[Main.java, Info]: Notification Voice Callsigns enabled: " + newValue);
}
});
grdPnlNotify.add(lblNotifyEnableSimpleSounds, 0, 1);
grdPnlNotify.add(chkBxEnableNotifySimpleSounds, 1, 1);
grdPnlNotify.add(lblNotifyEnableCWSounds, 0, 2);
grdPnlNotify.add(chkBxEnableNotifyCWSounds, 1, 2);
grdPnlNotify.add(lblNotifyEnableVoiceSounds, 0, 3);
grdPnlNotify.add(chkBxEnableNotifyVoiceSounds, 1, 3);
// grdPnlNotify.add(lblNitificationInfo, 0, 1);
// grdPnltrx.add(chkBxEnableTRXMsgbyUCX, 1, 1);
@@ -3163,6 +3208,7 @@ public class Kst4ContestApplication extends Application {
tblVw_shortcuts = initShortcutTable();
tblVw_shortcuts.setItems(this.chatcontroller.getChatPreferences().getLst_txtShortCutBtnList());
Button btn_Short_addLine = new Button("Add new shorcut-button");
btn_Short_addLine.setOnAction(new EventHandler<ActionEvent>() {
@Override
@@ -3439,23 +3485,51 @@ public class Kst4ContestApplication extends Application {
});
Button btnOptionspnlDisconnect = new Button("Disconnect & close Chat");
btnOptionspnlDisconnect.setDisable(false);
btnOptionspnlDisconnect.setOnAction(new EventHandler<ActionEvent>() {
@Override
public void handle(ActionEvent event) {
closeWindowEvent(null);
// chatcontroller.disconnect(ApplicationConstants.DISCSTRING_DISCONNECTONLY);
}
});
Button btnOptionspnlDisconnectOnly = new Button("Disconnect");
btnOptionspnlDisconnectOnly.setDisable(true);
menuItemFileDisconnect.setDisable(true);
btnOptionspnlDisconnect.setOnAction(new EventHandler<ActionEvent>() {
if (chatcontroller.isDisconnected()) {
btnOptionspnlDisconnectOnly.setDisable(true);
menuItemFileDisconnect.setDisable(true);
} else if (chatcontroller.isConnectedAndNOTLoggedIn()) {
btnOptionspnlDisconnectOnly.setDisable(true);
menuItemFileDisconnect.setDisable(true);
}
else if (chatcontroller.isConnectedAndLoggedIn()) {
btnOptionspnlDisconnectOnly.setDisable(false);
menuItemFileDisconnect.setDisable(false);
}
btnOptionspnlDisconnectOnly.setOnAction(new EventHandler<ActionEvent>() {
@Override
public void handle(ActionEvent event) {
closeWindowEvent(null);
// closeWindowEvent(null);
// System.out.println("UI: disc requested");
chatcontroller.disconnect(ApplicationConstants.DISCSTRING_DISCONNECTONLY);
txtFldCallSign.setDisable(false);
txtFldPassword.setDisable(false);
txtFldName.setDisable(false);
txtFldLocator.setDisable(false);
choiceBxChatChategory.setDisable(false);
btnOptionspnlConnect.setDisable(false);
btnOptionspnlDisconnect.setDisable(false);
btnOptionspnlDisconnectOnly.setDisable(true);
}
});
@@ -3491,6 +3565,10 @@ public class Kst4ContestApplication extends Application {
try {
chatcontroller.execute(); // TODO:THAT IS THE MAIN POINT WHERE THE CHAT WILL BE STARTED...MUST CATCH
// Passwordfailedexc in future
btnOptionspnlDisconnectOnly.setDisable(false);
menuItemFileDisconnect.setDisable(false);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();