<NME> KillServiceImplSpec.groovy
<BEF> /*
 *
 *  Copyright 2018 Netflix, Inc.
 *
 *     Licensed under the Apache License, Version 2.0 (the "License");
 *     you may not use this file except in compliance with the License.
 *     You may obtain a copy of the License at
 *
 *         http://www.apache.org/licenses/LICENSE-2.0
 *
 *     Unless required by applicable law or agreed to in writing, software
 *     distributed under the License is distributed on an "AS IS" BASIS,
 *     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *     See the License for the specific language governing permissions and
 *     limitations under the License.
 *
 */
package com.netflix.genie.agent.execution.services.impl

import com.netflix.genie.agent.execution.services.KillService
import com.netflix.genie.agent.execution.statemachine.ExecutionContext
import com.netflix.genie.agent.execution.statemachine.JobExecutionStateMachine
import spock.lang.Specification
import spock.lang.Unroll

class KillServiceImplSpec extends Specification {
    ApplicationEventPublisher applicationEventPublisher
    KillService service
    KillService service
    AgentProperties agentProperties
    ThreadFactory threadFactory

    void setup() {
        this.executionContext = Mock(ExecutionContext)
        this.stateMachine = Mock(JobExecutionStateMachine)
        this.agentProperties = new AgentProperties()
        this.threadFactory = Mock(ThreadFactory)
        this.service = new KillServiceImpl(executionContext, agentProperties, threadFactory)
    }

    @Unroll
    def "Kill via #source"() {
        when:
        service.kill(source)

        then:
        1 * executionContext.getStateMachine() >> stateMachine
        1 * stateMachine.kill(source)
        1 * threadFactory.newThread(_ as Runnable) >> Mock(Thread)

        KillService.KillSource.SYSTEM_SIGNAL    | _
        KillService.KillSource.API_KILL_REQUEST | _
    }
}

<MSG> Implement last-resort termination on agent

Implement a simple mechanism to ensure an agent asked to shut down does not get stuck and lives forever.
Kill service forks a thread that after a given amount of time will shut down the JVM (usage of thread is preferred to depending on executor services which may be stuck/busy/blocked).

<DFF> @@ -23,6 +23,9 @@ import org.springframework.context.ApplicationEventPublisher
 import spock.lang.Specification
 import spock.lang.Unroll
 
+import java.time.Duration
+import java.util.concurrent.atomic.AtomicBoolean
+
 class KillServiceImplSpec extends Specification {
     ApplicationEventPublisher applicationEventPublisher
     KillService service
@@ -51,4 +54,26 @@ class KillServiceImplSpec extends Specification {
         KillService.KillSource.SYSTEM_SIGNAL    | _
         KillService.KillSource.API_KILL_REQUEST | _
     }
+
+    def "Emergency termination"() {
+        setup:
+        AtomicBoolean emergencyTerminationExecuted = new AtomicBoolean(false)
+        service = new KillServiceImpl(
+            applicationEventPublisher,
+            { -> emergencyTerminationExecuted.set(true) },
+            Duration.ofMillis(10)
+        )
+
+        when:
+        service.kill(KillService.KillSource.SYSTEM_SIGNAL)
+
+        then:
+        1 * applicationEventPublisher.publishEvent(_ as KillService.KillEvent)
+
+        when:
+        sleep(100)
+
+        then:
+        emergencyTerminationExecuted.get()
+    }
 }

Implement last-resort termination on agent