TheBrig.java

1
package com.renomad.minum.security;
2
3
import com.renomad.minum.database.AbstractDb;
4
import com.renomad.minum.state.Context;
5
import com.renomad.minum.logging.ILogger;
6
import com.renomad.minum.utils.ThrowingRunnable;
7
import com.renomad.minum.utils.TimeUtils;
8
9
import java.io.IOException;
10
import java.util.*;
11
import java.util.concurrent.*;
12
import java.util.concurrent.locks.ReentrantLock;
13
14
/**
15
 * See {@link ITheBrig}
16
 */
17
public final class TheBrig implements ITheBrig {
18
    private final ExecutorService es;
19
    private final AbstractDb<Inmate> inmatesDb;
20
    private final ILogger logger;
21
22
    /**
23
     * This lock is around edits to the inmate database, so it is not
24
     * possible for two threads to be adding an inmate at the same
25
     * time, or deleting an inmate at the same time, or adding an
26
     * inmate while deleting an inmate.
27
     */
28
    private final ReentrantLock lock = new ReentrantLock();
29
    private Thread myThread;
30
    private static final String CLIENT_IDENTIFIER_INDEX = "client_identifier_index";
31
32
    /**
33
     * How long our inner thread will sleep before waking up to scan
34
     * for old keys
35
     */
36
    private final int sleepTime;
37
38
    public TheBrig(int sleepTime, Context context) {
39
        this.es = context.getExecutorService();
40
        this.logger = context.getLogger();
41
        this.inmatesDb = context.getDb2("the_brig", Inmate.EMPTY)
42
                .registerIndex(CLIENT_IDENTIFIER_INDEX, Inmate::getClientId)
43
                .loadData();
44
        this.sleepTime = sleepTime;
45
    }
46
47
    /**
48
     * In this class we create a thread that runs throughout the lifetime
49
     * of the application, in an infinite loop removing keys from the list
50
     * under consideration.
51
     */
52
    public TheBrig(Context context) {
53
        this(10 * 1000, context);
54
    }
55
56
    // Regarding the BusyWait - indeed, we expect that the while loop
57
    // below is an infinite loop unless there's an exception thrown, that's what it is.
58
    @Override
59
    public TheBrig initialize() {
60
        logger.logDebug(() -> "Initializing TheBrig main loop");
61
        ThrowingRunnable innerLoopThread = () -> {
62
            Thread.currentThread().setName("TheBrigThread");
63
            myThread = Thread.currentThread();
64
            while (true) {
65
                try {
66 1 1. lambda$initialize$2 : removed call to com/renomad/minum/security/TheBrig::reviewCurrentInmates → TIMED_OUT
                    reviewCurrentInmates();
67
                } catch (InterruptedException ex) {
68
69
                    /*
70
                    this is what we expect to happen.
71
                    once this happens, we just continue on.
72
                    this only gets called when we are trying to shut everything
73
                    down cleanly
74
                     */
75
76
                    logger.logDebug(() -> String.format("%s TheBrig is stopped.%n", TimeUtils.getTimestampIsoInstant()));
77
                    Thread.currentThread().interrupt();
78
                    break;
79
                }
80
            }
81
        };
82
        es.submit(ThrowingRunnable.throwingRunnableWrapper(innerLoopThread, logger));
83 1 1. initialize : replaced return value with null for com/renomad/minum/security/TheBrig::initialize → KILLED
        return this;
84
    }
85
86
    private void reviewCurrentInmates() throws InterruptedException {
87
        Collection<Inmate> values = inmatesDb.values();
88 1 1. reviewCurrentInmates : negated conditional → KILLED
        if (! values.isEmpty()) {
89
            logger.logTrace(() -> "TheBrig reviewing current inmates. Count: " + values.size());
90
        }
91
        var now = System.currentTimeMillis();
92 1 1. reviewCurrentInmates : removed call to com/renomad/minum/security/TheBrig::processInmateList → KILLED
        processInmateList(now, values, logger, inmatesDb, lock);
93
        Thread.sleep((long) sleepTime);
94
    }
95
96
    /**
97
     * figure out which clients have paid their dues
98
     *
99
     * @param now       the current time, in milliseconds past the epoch
100
     * @param inmatesDb the database of all inmates
101
     * @param lock      a lock around deleting any inmate being released,
102
     *                  to avoid conflicts with {@link #sendToJail(String, long)}
103
     */
104
    static void processInmateList(long now, Collection<Inmate> inmates, ILogger logger, AbstractDb<Inmate> inmatesDb, ReentrantLock lock) {
105
        for (Inmate clientKeyAndDuration : inmates) {
106
            // if the release time is in the past (that is, the release time is
107
            // before / less-than now), add them to the list to be released.
108 2 1. processInmateList : negated conditional → KILLED
2. processInmateList : changed conditional boundary → KILLED
            if (clientKeyAndDuration.getReleaseTime() < now) {
109
                logger.logTrace(() -> "UnderInvestigation: " + clientKeyAndDuration.getClientId() + " has paid its dues as of " + clientKeyAndDuration.getReleaseTime() + " and is getting released. Current time: " + now);
110 1 1. processInmateList : removed call to java/util/concurrent/locks/ReentrantLock::lock → KILLED
                lock.lock();
111
                try {
112 1 1. processInmateList : removed call to com/renomad/minum/database/AbstractDb::delete → KILLED
                    inmatesDb.delete(clientKeyAndDuration);
113
                } finally {
114 1 1. processInmateList : removed call to java/util/concurrent/locks/ReentrantLock::unlock → KILLED
                    lock.unlock();
115
                }
116
            }
117
        }
118
    }
119
120
    @Override
121
    public void stop() throws IOException {
122
        logger.logDebug(() -> "TheBrig has been told to stop");
123 1 1. stop : negated conditional → KILLED
        if (myThread != null) {
124
            logger.logDebug(() -> "TheBrig: Sending interrupt to thread");
125
            myThread.interrupt();
126 1 1. stop : removed call to com/renomad/minum/database/AbstractDb::stop → KILLED
            this.inmatesDb.stop();
127
        } else {
128
            throw new MinumSecurityException("TheBrig was told to stop, but it was uninitialized");
129
        }
130
    }
131
132
    @Override
133
    public boolean sendToJail(String clientIdentifier, long sentenceDuration) {
134 1 1. sendToJail : removed call to java/util/concurrent/locks/ReentrantLock::lock → KILLED
        lock.lock();
135
        try {
136
            long now = System.currentTimeMillis();
137
138
            Inmate existingInmate = inmatesDb.findExactlyOne(CLIENT_IDENTIFIER_INDEX, clientIdentifier);
139 1 1. sendToJail : negated conditional → KILLED
            if (existingInmate == null) {
140
                // if this is a new inmate, add them
141 1 1. sendToJail : Replaced long addition with subtraction → KILLED
                long releaseTime = now + sentenceDuration;
142
                logger.logDebug(() -> "TheBrig: Putting away " + clientIdentifier + " for " + sentenceDuration + " milliseconds. Release time: " + releaseTime + ". Current time: " + now);
143
                Inmate newInmate = new Inmate(0L, clientIdentifier, releaseTime);
144
                inmatesDb.write(newInmate);
145
            } else {
146
                // if this is an existing inmate continuing to attack us, just update their duration
147 1 1. sendToJail : Replaced long addition with subtraction → KILLED
                long releaseTime = existingInmate.getReleaseTime() + sentenceDuration;
148
                logger.logDebug(() -> "TheBrig: Putting away " + clientIdentifier + " for " + sentenceDuration + " milliseconds. Release time: " + releaseTime + ". Current time: " + now);
149
                inmatesDb.write(new Inmate(existingInmate.getIndex(), existingInmate.getClientId(), releaseTime));
150
            }
151
        } finally {
152 1 1. sendToJail : removed call to java/util/concurrent/locks/ReentrantLock::unlock → KILLED
            lock.unlock();
153
        }
154 1 1. sendToJail : replaced boolean return with false for com/renomad/minum/security/TheBrig::sendToJail → KILLED
        return true;
155
156
    }
157
158
    @Override
159
    public boolean isInJail(String clientIdentifier) {
160 2 1. isInJail : negated conditional → KILLED
2. isInJail : replaced boolean return with true for com/renomad/minum/security/TheBrig::isInJail → KILLED
            return inmatesDb.findExactlyOne(CLIENT_IDENTIFIER_INDEX, clientIdentifier) != null;
161
    }
162
163
    @Override
164
    public Collection<Inmate> getInmates() {
165 1 1. getInmates : replaced return value with Collections.emptyList for com/renomad/minum/security/TheBrig::getInmates → KILLED
            return inmatesDb.values();
166
    }
167
168
}

Mutations

66

1.1
Location : lambda$initialize$2
Killed by : none
removed call to com/renomad/minum/security/TheBrig::reviewCurrentInmates → TIMED_OUT

83

1.1
Location : initialize
Killed by : com.renomad.minum.FunctionalTests.test_PathFunction_Response(com.renomad.minum.FunctionalTests)
replaced return value with null for com/renomad/minum/security/TheBrig::initialize → KILLED

88

1.1
Location : reviewCurrentInmates
Killed by : com.renomad.minum.web.WebPerformanceTests.webPerfTest(com.renomad.minum.web.WebPerformanceTests)
negated conditional → KILLED

92

1.1
Location : reviewCurrentInmates
Killed by : com.renomad.minum.web.WebPerformanceTests.webPerfTest(com.renomad.minum.web.WebPerformanceTests)
removed call to com/renomad/minum/security/TheBrig::processInmateList → KILLED

108

1.1
Location : processInmateList
Killed by : com.renomad.minum.security.TheBrigTests
negated conditional → KILLED

2.2
Location : processInmateList
Killed by : com.renomad.minum.security.TheBrigTests
changed conditional boundary → KILLED

110

1.1
Location : processInmateList
Killed by : com.renomad.minum.security.TheBrigTests
removed call to java/util/concurrent/locks/ReentrantLock::lock → KILLED

112

1.1
Location : processInmateList
Killed by : com.renomad.minum.security.TheBrigTests
removed call to com/renomad/minum/database/AbstractDb::delete → KILLED

114

1.1
Location : processInmateList
Killed by : com.renomad.minum.security.TheBrigTests
removed call to java/util/concurrent/locks/ReentrantLock::unlock → KILLED

123

1.1
Location : stop
Killed by : com.renomad.minum.security.TheBrigTests
negated conditional → KILLED

126

1.1
Location : stop
Killed by : com.renomad.minum.security.TheBrigTests
removed call to com/renomad/minum/database/AbstractDb::stop → KILLED

134

1.1
Location : sendToJail
Killed by : com.renomad.minum.web.WebTests
removed call to java/util/concurrent/locks/ReentrantLock::lock → KILLED

139

1.1
Location : sendToJail
Killed by : com.renomad.minum.web.WebTests
negated conditional → KILLED

141

1.1
Location : sendToJail
Killed by : com.renomad.minum.web.WebTests
Replaced long addition with subtraction → KILLED

147

1.1
Location : sendToJail
Killed by : com.renomad.minum.security.TheBrigTests
Replaced long addition with subtraction → KILLED

152

1.1
Location : sendToJail
Killed by : com.renomad.minum.web.WebTests
removed call to java/util/concurrent/locks/ReentrantLock::unlock → KILLED

154

1.1
Location : sendToJail
Killed by : com.renomad.minum.web.WebTests
replaced boolean return with false for com/renomad/minum/security/TheBrig::sendToJail → KILLED

160

1.1
Location : isInJail
Killed by : com.renomad.minum.web.WebPerformanceTests.webPerfTest(com.renomad.minum.web.WebPerformanceTests)
negated conditional → KILLED

2.2
Location : isInJail
Killed by : com.renomad.minum.web.WebPerformanceTests.webPerfTest(com.renomad.minum.web.WebPerformanceTests)
replaced boolean return with true for com/renomad/minum/security/TheBrig::isInJail → KILLED

165

1.1
Location : getInmates
Killed by : com.renomad.minum.security.TheBrigTests
replaced return value with Collections.emptyList for com/renomad/minum/security/TheBrig::getInmates → KILLED

Active mutators

Tests examined


Report generated by PIT 1.17.0