summaryrefslogtreecommitdiff
path: root/src/threads/Semaphore.vala
blob: 05b1f1391df6864233ef7c49e0e47af05e6bded4 (plain)
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
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
/* Copyright 2016 Software Freedom Conservancy Inc.
 *
 * This software is licensed under the GNU LGPL (version 2.1 or later).
 * See the COPYING file in this distribution.
 */

// Semaphores may be used to be notified when a job is completed.  This provides an alternate
// mechanism (essentially, a blocking mechanism) to the system of callbacks that BackgroundJob
// offers.  They can also be used for other job-dependent notification mechanisms.
public abstract class AbstractSemaphore {
    public enum Type {
        SERIAL,
        BROADCAST
    }
    
    protected enum NotifyAction {
        NONE,
        SIGNAL
    }
    
    protected enum WaitAction {
        SLEEP,
        READY
    }
    
    private Type type;
    private Mutex mutex = Mutex();
    private Cond monitor = Cond();
    
    public AbstractSemaphore(Type type) {
        assert(type == Type.SERIAL || type == Type.BROADCAST);
        
        this.type = type;
    }
    
    private void trigger() {
        if (type == Type.SERIAL)
            monitor.signal();
        else
            monitor.broadcast();
    }
    
    public void notify() {
        mutex.lock();
        
        NotifyAction action = do_notify();
        switch (action) {
            case NotifyAction.NONE:
                // do nothing
            break;
            
            case NotifyAction.SIGNAL:
                trigger();
            break;
            
            default:
                error("Unknown semaphore action: %s", action.to_string());
        }
        
        mutex.unlock();
    }
    
    // This method is called by notify() with the semaphore's mutex locked.
    protected abstract NotifyAction do_notify();
    
    public void wait() {
        mutex.lock();
        
        while (do_wait() == WaitAction.SLEEP)
            monitor.wait(mutex);
        
        mutex.unlock();
    }
    
    // This method is called by wait() with the semaphore's mutex locked.
    protected abstract WaitAction do_wait();
    
    // Returns true if the semaphore is reset, false otherwise.
    public bool reset() {
        mutex.lock();
        bool is_reset = do_reset();
        mutex.unlock();
        
        return is_reset;
    }
    
    // This method is called by reset() with the semaphore's mutex locked.  Returns true if reset,
    // false if not supported.
    protected virtual bool do_reset() {
        return false;
    }
}

public class Semaphore : AbstractSemaphore {
    bool passed = false;
    
    public Semaphore() {
        base (AbstractSemaphore.Type.BROADCAST);
    }
    
    protected override AbstractSemaphore.NotifyAction do_notify() {
        if (passed)
            return NotifyAction.NONE;
        
        passed = true;
        
        return NotifyAction.SIGNAL;
    }
    
    protected override AbstractSemaphore.WaitAction do_wait() {
        return passed ? WaitAction.READY : WaitAction.SLEEP;
    }
}

public class CountdownSemaphore : AbstractSemaphore {
    private int total;
    private int passed = 0;
    
    public CountdownSemaphore(int total) {
        base (AbstractSemaphore.Type.BROADCAST);
        
        this.total = total;
    }
    
    protected override AbstractSemaphore.NotifyAction do_notify() {
        if (passed >= total)
            critical("CountdownSemaphore overrun: %d/%d", passed + 1, total);
        
        return (++passed >= total) ? NotifyAction.SIGNAL : NotifyAction.NONE;
    }
    
    protected override AbstractSemaphore.WaitAction do_wait() {
        return (passed < total) ? WaitAction.SLEEP : WaitAction.READY;
    }
}

public class EventSemaphore : AbstractSemaphore {
    bool fired = false;
    
    public EventSemaphore() {
        base (AbstractSemaphore.Type.BROADCAST);
    }
    
    protected override AbstractSemaphore.NotifyAction do_notify() {
        fired = true;
        
        return NotifyAction.SIGNAL;
    }
    
    protected override AbstractSemaphore.WaitAction do_wait() {
        return fired ? WaitAction.READY : WaitAction.SLEEP;
    }
    
    protected override bool do_reset() {
        fired = false;
        
        return true;
    }
}