summaryrefslogtreecommitdiff
path: root/src/main/java/com/it_jaros/jscanner/scan/engine/ProducerThread.java
blob: e7bcbcc1ddc86f09f7fb71e39ea94d8e99ae65d6 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
package com.it_jaros.jscanner.scan.engine;

import java.time.Duration;
import java.util.Iterator;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Function;

public class ProducerThread {

    public static final Duration pollInterval = Duration.ofSeconds(1);

    private final ExecutorService executor;
    private final CancelledToken cancelledToken;

    public ProducerThread(ScanExecutionContext context) {
        this.executor = context.executorService();
        this.cancelledToken = context.cancelledToken();
    }

    /**
     * This method helps to cleanup the code a bit and remove redundancy
     * The producer for providing hosts and the one for providing ports
     * are similar and the small differences can be handled using a function
     *
     * @param queue
     * @param maxWorkers
     * @param taskFactory
     * @param <INPUT>
     * @param <OUTPUT>
     * @return
     */
    public <INPUT, OUTPUT> ProducerState<OUTPUT> startProducer(
            Iterator<INPUT> queue,
            int maxWorkers,
            Function<INPUT, Callable<OUTPUT>> taskFactory
    ) {
        final AtomicInteger inPipeline = new AtomicInteger(0);
        final AtomicBoolean running = new AtomicBoolean(true);
        final Semaphore activeWorkers = new Semaphore(maxWorkers);
        CompletionService<OUTPUT> completionService = new ExecutorCompletionService<>(executor);
        executor.submit(() -> {
            try {
                while (!cancelledToken.isCancelled() && queue.hasNext()) {
                    // get semaphore and remember if task got submitted
                    // so in case we fail to submit we release the semaphore
                    activeWorkers.acquire();
                    boolean isTaskSubmitted = false;
                    try {
                        // just in case something
                        // changed while waiting
                        if (cancelledToken.isCancelled()) {
                            break;
                        }

                        // get next item and create callable
                        // using lambda expression
                        final INPUT item = queue.next();
                        Callable<OUTPUT> task = taskFactory.apply(item);
                        inPipeline.incrementAndGet();
                        try {
                            completionService.submit(task);
                            isTaskSubmitted = true;
                        } catch (Throwable e) {
                            inPipeline.decrementAndGet();
                            throw e;
                        }
                    } finally {
                        if (!isTaskSubmitted) {
                            activeWorkers.release();
                        }
                    }
                }
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            } finally {
                running.set(false);
            }
        });
        return new ProducerState<>(running, inPipeline, activeWorkers, completionService);
    }
}