PageRenderTime 114ms CodeModel.GetById 30ms app.highlight 42ms RepoModel.GetById 38ms app.codeStats 0ms

/mordor/future.h

http://github.com/mozy/mordor
C Header | 282 lines | 231 code | 22 blank | 29 comment | 56 complexity | 55ca50bdfd347e5d6901585e27b9b072 MD5 | raw file
  1#ifndef __MORDOR_FUTURE_H__
  2#define __MORDOR_FUTURE_H__
  3// Copyright (c) 2009 - Mozy, Inc.
  4
  5#include <bitset>
  6
  7#include <boost/bind.hpp>
  8#include <boost/function.hpp>
  9#include <boost/noncopyable.hpp>
 10
 11#include "assert.h"
 12#include "atomic.h"
 13#include "fiber.h"
 14#include "scheduler.h"
 15
 16namespace Mordor {
 17
 18struct Void;
 19
 20template <class T = Void>
 21class Future : boost::noncopyable
 22{
 23    template <class Iterator>
 24    friend void waitAll(Iterator start, Iterator end);
 25    template <class Iterator>
 26    friend size_t waitAny(Iterator start, Iterator end);
 27public:
 28    Future(boost::function<void (const T &)> dg = NULL, Scheduler *scheduler = NULL)
 29        : m_fiber(0),
 30          m_scheduler(scheduler),
 31          m_dg(dg),
 32          m_t()
 33    {
 34        MORDOR_ASSERT(dg || !scheduler);
 35        if (m_dg)
 36            m_fiber = 0x2;
 37    }
 38
 39    /// Suspend this Fiber, and wait for the future to become signalled
 40    T &wait()
 41    {
 42        MORDOR_ASSERT(!m_scheduler);
 43        MORDOR_ASSERT(!m_dg);
 44        m_scheduler = Scheduler::getThis();
 45        intptr_t newValue = (intptr_t)Fiber::getThis().get();
 46        MORDOR_ASSERT(!(newValue & 0x1));
 47        intptr_t currentValue = atomicCompareAndSwap(m_fiber, newValue, (intptr_t)0);
 48        // Not signalled yet
 49        if (currentValue == 0) {
 50            Scheduler::yieldTo();
 51            // Make sure we got signalled
 52            MORDOR_ASSERT(m_fiber & 0x1);
 53        } else if (currentValue == 0x1) {
 54            // Already signalled; just return
 55        } else {
 56            MORDOR_NOTREACHED();
 57        }
 58        return m_t;
 59    }
 60
 61    /// For signallers to set the result; once signalled should not be modified
 62    T& result() { MORDOR_ASSERT(!(m_fiber & 0x1)); return m_t; }
 63
 64    /// Signal this Future
 65    void signal()
 66    {
 67        intptr_t newValue = m_fiber, oldValue;
 68        if (newValue == 0x2) {
 69            MORDOR_ASSERT(m_dg);
 70            if (m_scheduler)
 71                m_scheduler->schedule(boost::bind(m_dg, boost::cref(m_t)));
 72            else
 73                m_dg(m_t);
 74            return;
 75        }
 76        do {
 77            oldValue = newValue;
 78            newValue = oldValue | 0x1;
 79        } while ( (newValue = atomicCompareAndSwap(m_fiber, newValue, oldValue)) != oldValue);
 80        newValue &= ~0x1;
 81        // Somebody was previously waiting
 82        if (newValue) {
 83            MORDOR_ASSERT(m_scheduler);
 84            MORDOR_ASSERT(!m_dg);
 85            m_scheduler->schedule(((Fiber *)newValue)->shared_from_this());
 86        }
 87    }
 88
 89    void reset()
 90    {
 91        m_fiber = 0;
 92        if (!m_dg)
 93            m_scheduler = NULL;
 94    }
 95
 96private:
 97    // We're going to stuff a couple of things into m_fiber, and do some bit
 98    // manipulation, so it's going to be easier to declare it as intptr_t
 99    // m_fiber = NULL if not signalled, and not waiting
100    // m_fiber & 0x1 if signalled
101    // m_fiber & ~0x1 if waiting
102    // Note that m_fiber will *not* point to a fiber if m_dg is valid
103    intptr_t m_fiber;
104    Scheduler *m_scheduler;
105    boost::function<void (const T &)> m_dg;
106    T m_t;
107};
108
109template <>
110class Future<Void> : boost::noncopyable
111{
112    template <class Iterator>
113    friend void waitAll(Iterator start, Iterator end);
114    template <class Iterator>
115    friend size_t waitAny(Iterator start, Iterator end);
116public:
117    Future(boost::function<void ()> dg = NULL, Scheduler *scheduler = NULL)
118        : m_fiber(0),
119          m_scheduler(scheduler),
120          m_dg(dg)
121    {
122        MORDOR_ASSERT(m_dg || !scheduler);
123        if (m_dg)
124            m_fiber = 0x2;
125    }
126
127    /// Suspend this Fiber, and wait for the future to become signalled
128    void wait()
129    {
130        MORDOR_ASSERT(!m_scheduler);
131        MORDOR_ASSERT(!m_dg);
132        m_scheduler = Scheduler::getThis();
133        intptr_t newValue = (intptr_t)Fiber::getThis().get();
134        MORDOR_ASSERT(!(newValue & 0x1));
135        intptr_t currentValue = atomicCompareAndSwap(m_fiber, newValue, (intptr_t)0);
136        // Not signalled yet
137        if (currentValue == 0) {
138            Scheduler::yieldTo();
139            // Make sure we got signalled
140            MORDOR_ASSERT(m_fiber & 0x1);
141        } else if (currentValue == 0x1) {
142            // Already signalled; just return
143        } else {
144            MORDOR_NOTREACHED();
145        }
146    }
147
148    /// Signal this Future
149    void signal()
150    {
151        intptr_t newValue = m_fiber, oldValue;
152        if (newValue == 0x2) {
153            MORDOR_ASSERT(m_dg);
154            if (m_scheduler)
155                m_scheduler->schedule(m_dg);
156            else
157                m_dg();
158            return;
159        }
160        do {
161            oldValue = newValue;
162            newValue = oldValue | 0x1;
163        } while ( (newValue = atomicCompareAndSwap(m_fiber, newValue, oldValue)) != oldValue);
164        newValue &= ~0x1;
165        // Somebody was previously waiting
166        if (newValue) {
167            MORDOR_ASSERT(m_scheduler);
168            MORDOR_ASSERT(!m_dg);
169            m_scheduler->schedule(((Fiber *)newValue)->shared_from_this());
170        }
171    }
172
173    void reset()
174    {
175        m_fiber = 0;
176        if (!m_dg)
177            m_scheduler = NULL;
178    }
179
180private:
181    /// @return If the future was already signalled
182    bool startWait()
183    {
184        MORDOR_ASSERT(!m_scheduler);
185        MORDOR_ASSERT(!m_dg);
186        m_scheduler = Scheduler::getThis();
187        intptr_t newValue = (intptr_t)Fiber::getThis().get();
188        MORDOR_ASSERT(!(newValue & 0x1));
189        intptr_t currentValue = atomicCompareAndSwap(m_fiber, newValue, (intptr_t)0);
190        if (currentValue == 0) {
191            return false;
192        } else if (currentValue == 0x1) {
193            return true;
194        } else {
195            MORDOR_NOTREACHED();
196        }
197    }
198
199    /// @return If the future was already signalled
200    bool cancelWait()
201    {
202        MORDOR_ASSERT(!m_dg);
203        intptr_t oldValue = m_fiber;
204        if (oldValue & 1)
205            return true;
206        oldValue = atomicCompareAndSwap(m_fiber, (intptr_t)0, oldValue);
207        if (oldValue & 1)
208            return true;
209        m_scheduler = NULL;
210        return false;
211    }
212
213private:
214    // We're going to stuff a couple of things into m_fiber, and do some bit
215    // manipulation, so it's going to be easier to declare it as intptr_t
216    // m_fiber = NULL if not signalled, and not waiting
217    // m_fiber & 0x1 if signalled
218    // m_fiber & ~0x1 if waiting
219    // Note that m_fiber will *not* point to a fiber if m_dg is valid
220    intptr_t m_fiber;
221    Scheduler *m_scheduler;
222    boost::function<void ()> m_dg;
223};
224
225template <class Iterator>
226void waitAll(Iterator first, Iterator last)
227{
228    MORDOR_ASSERT(first != last);
229    size_t yieldsNeeded = 1;
230    for (; first != last; ++first) {
231        if (!first->startWait())
232            ++yieldsNeeded;
233    }
234    while (--yieldsNeeded) Scheduler::yieldTo();
235}
236
237template <class Iterator>
238size_t waitAny(Iterator first, Iterator last)
239{
240    MORDOR_ASSERT(first != last);
241    size_t result = ~0u;
242    size_t index = 0u;
243    // Optimize first one
244    if (first->startWait())
245        return 0;
246    Iterator it = first;
247    ++it;
248    while (it != last) {
249        ++index;
250        if (it->startWait()) {
251            --it;
252            result = index;
253            break;
254        }
255        ++it;
256    }
257    size_t yieldsNeeded = 1;
258    if (it == last) {
259        --yieldsNeeded;
260        Scheduler::yieldTo();
261        --it;
262    }
263    while (it != first) {
264        if (it->cancelWait()) {
265            result = index;
266            ++yieldsNeeded;
267        }
268        --it;
269        --index;
270    }
271    if (it->cancelWait()) {
272        result = 0;
273        ++yieldsNeeded;
274    }
275    MORDOR_ASSERT(result != ~0u);
276    while (--yieldsNeeded) Scheduler::yieldTo();
277    return result;
278}
279
280}
281
282#endif