Skip to content
This repository was archived by the owner on Aug 18, 2020. It is now read-only.

Commit b94f449

Browse files
committed
Added proper shutdown functionality of unused inputs, outputs and their connectors. Updated API and framework.
1 parent 56202c7 commit b94f449

File tree

17 files changed

+227
-53
lines changed

17 files changed

+227
-53
lines changed

src/main/scala/org/codeoverflow/chatoverflow/Launcher.scala

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ object Launcher extends WithLogger {
1111

1212
var server: Option[Server] = None
1313
var pluginDataPath: String = "data"
14+
private var chatOverflow: Option[ChatOverflow] = None
1415

1516
/**
1617
* Software entry point.
@@ -24,6 +25,7 @@ object Launcher extends WithLogger {
2425
// The complete project visible trough one single instance
2526
val chatOverflow = new ChatOverflow(config.pluginFolderPath,
2627
config.configFolderPath, config.requirementPackage, config.pluginLogOutputOnConsole)
28+
this.chatOverflow = Some(chatOverflow)
2729

2830
// Initialize chat overflow
2931
chatOverflow.init()
@@ -68,7 +70,25 @@ object Launcher extends WithLogger {
6870
* Shuts down the framework.
6971
*/
7072
def exit(): Unit = {
71-
logger info "Shutting down Chat Overflow."
73+
logger debug "Shutting down Chat Overflow."
74+
if (chatOverflow.isDefined) {
75+
logger debug "Trying to stop all running instances..."
76+
chatOverflow.get.pluginInstanceRegistry.getAllPluginInstances.
77+
filter(_.isRunning).foreach(_.stopPlease())
78+
79+
for (i <- 1 to 5) {
80+
if (chatOverflow.get.pluginInstanceRegistry.getAllPluginInstances.
81+
exists(_.isRunning)) {
82+
logger info s"Check $i of 5. Still waiting on: ${
83+
chatOverflow.get.pluginInstanceRegistry.getAllPluginInstances.
84+
filter(_.isRunning).map(_.instanceName).mkString(", ")
85+
}"
86+
Thread.sleep(1000)
87+
}
88+
}
89+
}
90+
91+
logger info "Bye Bye. Stay minzig!"
7292
System.exit(0)
7393
}
7494
}

src/main/scala/org/codeoverflow/chatoverflow/connector/Connector.scala

Lines changed: 23 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@ abstract class Connector(val sourceIdentifier: String) extends WithLogger {
2525
protected var optionalCredentialKeys: List[String]
2626
protected var running = false
2727

28+
private var instanceCount = 0
29+
2830
def getCredentials: Option[Credentials] = this.credentials
2931

3032
/**
@@ -76,6 +78,7 @@ abstract class Connector(val sourceIdentifier: String) extends WithLogger {
7678
// Check if running
7779
if (running) {
7880
logger info s"Unable to start $connectorSourceAndType. Already running!"
81+
changeInstanceCount(1)
7982
true
8083
} else {
8184

@@ -101,6 +104,7 @@ abstract class Connector(val sourceIdentifier: String) extends WithLogger {
101104

102105
if (start()) {
103106
logger info s"Started $connectorSourceAndType."
107+
changeInstanceCount(1)
104108
running = true
105109
true
106110
} else {
@@ -127,15 +131,29 @@ abstract class Connector(val sourceIdentifier: String) extends WithLogger {
127131
/**
128132
* Shuts down the connector by calling the stop method.
129133
*/
130-
def shutdown(): Unit = {
131-
if (stop()) {
132-
running = false
133-
logger info s"Stopped $connectorSourceAndType."
134+
def shutdown(): Boolean = {
135+
changeInstanceCount(-1)
136+
137+
if (instanceCount <= 0) {
138+
logger info "Instance count is zero. Connector is no longer needed and shut down. RIP."
139+
if (stop()) {
140+
running = false
141+
logger info s"Stopped $connectorSourceAndType."
142+
true
143+
} else {
144+
logger warn s"Unable to shutdown $connectorSourceAndType."
145+
false
146+
}
134147
} else {
135-
logger warn s"Unable to shutdown $connectorSourceAndType."
148+
true // Return true to keep transparency
136149
}
137150
}
138151

152+
private def changeInstanceCount(delta: Int): Unit = {
153+
instanceCount += delta
154+
logger info s"Instance count: $instanceCount"
155+
}
156+
139157
/**
140158
* Returns the unique type string of the implemented connector.
141159
*

src/main/scala/org/codeoverflow/chatoverflow/instance/PluginInstance.scala

Lines changed: 20 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -73,12 +73,6 @@ class PluginInstance(val instanceName: String, pluginType: PluginType) extends W
7373
new PluginManagerStub()
7474
}
7575

76-
/**
77-
* Returns, if the plugin instance is created. This should be always the case when a plugin instance
78-
* is added to the registry.
79-
*/
80-
private def isCreated: Boolean = plugin.isDefined
81-
8276
/**
8377
* Creates a new thread and tries to start the plugin.
8478
* Make sure to set the requirements first!
@@ -183,6 +177,20 @@ class PluginInstance(val instanceName: String, pluginType: PluginType) extends W
183177
case e: AbstractMethodError => logger.error(s"Plugin '$instanceName' just crashed. Looks like a plugin version error.", e)
184178
case e: Exception => logger.error(s"Plugin '$instanceName' just had an exception. Might be a plugin implementation fault.", e)
185179
case e: Throwable => logger.error(s"Plugin '$instanceName' just crashed. We don't know whats going up here!", e)
180+
181+
} finally {
182+
183+
// If the plugin ended or crashed, try to shut down the connectors
184+
val inputRequirements = getRequirements.getInputRequirements.toArray
185+
inputRequirements.foreach(requirement => {
186+
requirement.asInstanceOf[Requirement[Input]].get().shutdown()
187+
})
188+
189+
val outputRequirements = getRequirements.getOutputRequirements.toArray
190+
outputRequirements.foreach(requirement => {
191+
requirement.asInstanceOf[Requirement[Output]].get().shutdown()
192+
})
193+
186194
}
187195
})
188196
instanceThread.start()
@@ -198,6 +206,12 @@ class PluginInstance(val instanceName: String, pluginType: PluginType) extends W
198206
}
199207
}
200208

209+
/**
210+
* Returns, if the plugin instance is created. This should be always the case when a plugin instance
211+
* is added to the registry.
212+
*/
213+
private def isCreated: Boolean = plugin.isDefined
214+
201215
/**
202216
* Returns the requirements object of the specific plugin instance.
203217
*

src/main/scala/org/codeoverflow/chatoverflow/requirement/InputImpl.scala

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,9 @@ import scala.reflect.ClassTag
99
abstract class InputImpl[C <: Connector](implicit ct: ClassTag[C]) extends Connection[C] with Input with WithLogger {
1010

1111
/**
12-
* Inits this connection, checks if teh source connector is defined, and can be inited, then calls start
12+
* Init this connection, checks if the source connector is defined, and can be inited, then calls start
1313
*
14-
* @return if this input could be successfully inited
14+
* @return if this input could be successfully initialized
1515
*/
1616
override def init(): Boolean = {
1717
if (sourceConnector.isDefined) {
@@ -24,6 +24,20 @@ abstract class InputImpl[C <: Connector](implicit ct: ClassTag[C]) extends Conne
2424
}
2525
}
2626

27+
/**
28+
* Shuts down the connection by calling stop on the input and requesting shutdown on the connector.
29+
*
30+
* @return true if both parties tried to shut down
31+
*/
32+
override def shutdown(): Boolean = {
33+
if (sourceConnector.isDefined) {
34+
stop() & sourceConnector.get.shutdown()
35+
} else {
36+
logger warn "Source connector not set."
37+
false
38+
}
39+
}
40+
2741

2842
/**
2943
* Start the input, called after source connector did init
@@ -32,6 +46,13 @@ abstract class InputImpl[C <: Connector](implicit ct: ClassTag[C]) extends Conne
3246
*/
3347
def start(): Boolean
3448

49+
/**
50+
* Stops the input, called before source connector will shutdown
51+
*
52+
* @return true if stopping was successful
53+
*/
54+
def stop(): Boolean
55+
3556

3657
/**
3758
* Serializes this object into a string to save it to a config

src/main/scala/org/codeoverflow/chatoverflow/requirement/OutputImpl.scala

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,9 @@ import scala.reflect.ClassTag
99
abstract class OutputImpl[C <: Connector](implicit ct: ClassTag[C]) extends Connection[C] with Output with WithLogger {
1010

1111
/**
12-
* Inits this connection, checks if teh source connector is defined, and can be inited, then calls start
12+
* Init this connection, checks if teh source connector is defined, and can be inited, then calls start
1313
*
14-
* @return if this input could be successfully inited
14+
* @return if this input could be successfully initialized
1515
*/
1616
override def init(): Boolean = {
1717
if (sourceConnector.isDefined) {
@@ -24,6 +24,19 @@ abstract class OutputImpl[C <: Connector](implicit ct: ClassTag[C]) extends Conn
2424
}
2525
}
2626

27+
/**
28+
* Shuts down the connection by calling stop on the output and requesting shutdown on the connector.
29+
*
30+
* @return true if both parties tried to shut down
31+
*/
32+
override def shutdown(): Boolean = {
33+
if (sourceConnector.isDefined) {
34+
stop() & sourceConnector.get.shutdown()
35+
} else {
36+
logger warn "Source connector not set."
37+
false
38+
}
39+
}
2740

2841
/**
2942
* Start the input, called after source connector did init
@@ -32,6 +45,13 @@ abstract class OutputImpl[C <: Connector](implicit ct: ClassTag[C]) extends Conn
3245
*/
3346
def start(): Boolean
3447

48+
/**
49+
* Stops the output, called before source connector will shutdown
50+
*
51+
* @return true if stopping was successful
52+
*/
53+
def stop(): Boolean
54+
3555

3656
/**
3757
* Serializes this object into a string to save it to a config

src/main/scala/org/codeoverflow/chatoverflow/requirement/service/discord/impl/DiscordChatInputImpl.scala

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -238,6 +238,13 @@ class DiscordChatInputImpl extends InputImpl[DiscordChatConnector] with DiscordC
238238
}
239239
}
240240
}
241+
242+
/**
243+
* Stops the input, called before source connector will shutdown
244+
*
245+
* @return true if stopping was successful
246+
*/
247+
override def stop(): Boolean = true
241248
}
242249

243250
object DiscordChatInputImpl {

src/main/scala/org/codeoverflow/chatoverflow/requirement/service/discord/impl/DiscordChatOutputImpl.scala

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,4 +52,11 @@ class DiscordChatOutputImpl extends OutputImpl[DiscordChatConnector] with Discor
5252
override def sendFile(file: String): Unit = sourceConnector.get.sendFile(getChannelId, file)
5353

5454
override def sendFile(file: String, message: String): Unit = sourceConnector.get.sendFile(getChannelId, file, Some(message))
55+
56+
/**
57+
* Stops the output, called before source connector will shutdown
58+
*
59+
* @return true if stopping was successful
60+
*/
61+
override def stop(): Boolean = true
5562
}

src/main/scala/org/codeoverflow/chatoverflow/requirement/service/file/impl/FileInputImpl.scala

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,22 +14,25 @@ import org.codeoverflow.chatoverflow.requirement.service.file.FileConnector
1414
@Impl(impl = classOf[FileInput], connector = classOf[FileConnector])
1515
class FileInputImpl extends InputImpl[FileConnector] with FileInput with WithLogger {
1616

17-
override def init(): Boolean = {
18-
sourceConnector.get.init()
19-
}
20-
2117
override def getFile(pathInResources: String): Optional[String] = Optional.ofNullable(sourceConnector.get.getFile(pathInResources).orNull)
2218

2319
override def getBinaryFile(pathInResources: String): Optional[Array[Byte]] = Optional.ofNullable(sourceConnector.get.getBinaryFile(pathInResources).orNull)
2420

2521
override def getImage(pathInResources: String): Optional[BufferedImage] = {
2622
val data = sourceConnector.get.getBinaryFile(pathInResources)
27-
if (!data.isDefined) {
23+
if (data.isEmpty) {
2824
None
2925
}
3026
val bis = new ByteArrayInputStream(data.get)
3127
Optional.of(ImageIO.read(bis))
3228
}
3329

3430
override def start(): Boolean = true
31+
32+
/**
33+
* Stops the input, called before source connector will shutdown
34+
*
35+
* @return true if stopping was successful
36+
*/
37+
override def stop(): Boolean = true
3538
}

src/main/scala/org/codeoverflow/chatoverflow/requirement/service/file/impl/FileOutputImpl.scala

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,6 @@ import org.codeoverflow.chatoverflow.requirement.service.file.FileConnector
1313
@Impl(impl = classOf[FileOutput], connector = classOf[FileConnector])
1414
class FileOutputImpl extends OutputImpl[FileConnector] with FileOutput with WithLogger {
1515

16-
override def init() = {
17-
sourceConnector.get.init()
18-
}
19-
2016
override def saveFile(content: String, pathInResources: String): Boolean = {
2117
sourceConnector.get.saveFile(pathInResources, content)
2218
}
@@ -45,4 +41,10 @@ class FileOutputImpl extends OutputImpl[FileConnector] with FileOutput with With
4541

4642
override def start() = true
4743

44+
/**
45+
* Stops the output, called before source connector will shutdown
46+
*
47+
* @return true if stopping was successful
48+
*/
49+
override def stop(): Boolean = true
4850
}

src/main/scala/org/codeoverflow/chatoverflow/requirement/service/mockup/impl/MockUpChatInputImpl.scala

Lines changed: 18 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -7,15 +7,15 @@ import org.codeoverflow.chatoverflow.WithLogger
77
import org.codeoverflow.chatoverflow.api.io.dto.chat.{Channel, ChatEmoticon, ChatMessage, ChatMessageAuthor}
88
import org.codeoverflow.chatoverflow.api.io.input.chat.MockUpChatInput
99
import org.codeoverflow.chatoverflow.registry.Impl
10-
import org.codeoverflow.chatoverflow.requirement.Connection
10+
import org.codeoverflow.chatoverflow.requirement.InputImpl
1111
import org.codeoverflow.chatoverflow.requirement.service.mockup.MockUpChatConnector
1212

1313
import scala.collection.JavaConverters._
1414
import scala.collection.mutable.ListBuffer
1515

1616
@Deprecated
1717
@Impl(impl = classOf[MockUpChatInput], connector = classOf[MockUpChatConnector])
18-
class MockUpChatInputImpl extends Connection[MockUpChatConnector] with MockUpChatInput with WithLogger {
18+
class MockUpChatInputImpl extends InputImpl[MockUpChatConnector] with MockUpChatInput with WithLogger {
1919

2020
private val messages: ListBuffer[ChatMessage[ChatMessageAuthor, Channel, ChatEmoticon]] = ListBuffer[ChatMessage[ChatMessageAuthor, Channel, ChatEmoticon]]()
2121
private val privateMessages: ListBuffer[ChatMessage[ChatMessageAuthor, Channel, ChatEmoticon]] = ListBuffer[ChatMessage[ChatMessageAuthor, Channel, ChatEmoticon]]()
@@ -40,22 +40,26 @@ class MockUpChatInputImpl extends Connection[MockUpChatConnector] with MockUpCha
4040

4141
override def registerPrivateMessageHandler(handler: Consumer[ChatMessage[ChatMessageAuthor, Channel, ChatEmoticon]]): Unit = privateMessageHandler += handler
4242

43-
override def init(): Boolean = {
44-
if (sourceConnector.isDefined) {
45-
sourceConnector.get.addMessageEventListener(onMessage)
46-
sourceConnector.get.init()
47-
} else {
48-
logger warn "Source connector not set."
49-
false
50-
}
51-
}
43+
override def serialize(): String = getSourceIdentifier
44+
45+
override def deserialize(value: String): Unit = setSourceConnector(value)
46+
47+
/**
48+
* Start the input, called after source connector did init
49+
*
50+
* @return true if starting the input was successful, false if some problems occurred
51+
*/
52+
override def start(): Boolean = true
5253

5354
private def onMessage(msg: ChatMessage[ChatMessageAuthor, Channel, ChatEmoticon]): Unit = {
5455
messageHandler.foreach(consumer => consumer.accept(msg))
5556
messages += msg
5657
}
5758

58-
override def serialize(): String = getSourceIdentifier
59-
60-
override def deserialize(value: String): Unit = setSourceConnector(value)
59+
/**
60+
* Stops the input, called before source connector will shutdown
61+
*
62+
* @return true if stopping was successful
63+
*/
64+
override def stop(): Boolean = true
6165
}

src/main/scala/org/codeoverflow/chatoverflow/requirement/service/sample/impl/SampleInputImpl.scala

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,4 +10,11 @@ import org.codeoverflow.chatoverflow.requirement.service.sample.SampleConnector
1010
class SampleInputImpl extends InputImpl[SampleConnector] with SampleInput with WithLogger {
1111

1212
override def start(): Boolean = true
13+
14+
/**
15+
* Stops the input, called before source connector will shutdown
16+
*
17+
* @return true if stopping was successful
18+
*/
19+
override def stop(): Boolean = true
1320
}

0 commit comments

Comments
 (0)