source: libcfa/src/concurrency/once.hfa @ b797d978

ADTast-experimental
Last change on this file since b797d978 was e8b8e65, checked in by Thierry Delisle <tdelisle@…>, 2 years ago

Added implementation of call_once

  • Property mode set to 100644
File size: 3.0 KB
RevLine 
[e8b8e65]1//
2// Cforall Version 1.0.0 Copyright (C) 2016 University of Waterloo
3//
4// The contents of this file are covered under the licence agreement in the
5// file "LICENCE" distributed with Cforall.
6//
7// once.hfa -- Algorithms to prevent concurrent calls to cause duplicate calls
8//
9// Author           : Thierry Delisle
10// Created On       : Thu Oct 11:40:47 2022
11// Last Modified By :
12// Last Modified On :
13// Update Count     :
14//
15
16#pragma once
17
18#include "containers/lockfree.hfa"
19#include "kernel/fwd.hfa"
20
21enum once_state {
22        ARMED = 0,
23        IN_PROGRESS,
24        READY
25};
26
27struct once_flag {
28        volatile int state;
29        poison_list( thread$ ) waiters;
30};
31
32static inline {
33        void ?{}(once_flag & this) { this.state = ARMED; }
34
35        void once_wait$(once_flag & this) {
36                // just push the thread to the list
37                if(push( this.waiters, active_thread() )) {
38                        // the list wasn't poisoned, push was successful, just park.
39                        park();
40                }
41        }
42
43        void once_call$( once_flag & this, void (*func)(void) ) {
44                /* paranoid */ verify( once_state.IN_PROGRESS == __atomic_load_n(&this.state, __ATOMIC_RELAXED) );
45                /* paranoid */ verify( ! is_poisoned(this.waiters) );
46
47                // call the thing we are here for!
48                func();
49
50                /* paranoid */ verify( ! is_poisoned(this.waiters) );
51                /* paranoid */ verify( once_state.IN_PROGRESS == __atomic_load_n(&this.state, __ATOMIC_RELAXED) );
52
53                // Mark the call as being done.
54                __atomic_store_n( &this.state, (int)once_state.IN_PROGRESS, __ATOMIC_SEQ_CST );
55
56                // wake up the sleepers and make sure no new sleeper arrives
57                thread$ * sleeper = poison( this.waiters );
58
59                /* paranoid */ verify( ! is_poisoned(this.waiters) );
60                /* paranoid */ verify( once_state.READY == __atomic_load_n(&this.state, __ATOMIC_RELAXED) );
61
62                while(sleeper != 0p) {
63                        // find the next thread now because unpark invalidates the pointer
64                        thread$ * next = advance(sleeper);
65
66                        // wake-up the thread, invalidates pointer
67                        unpark( sleeper );
68
69                        // update the current
70                        sleeper = next;
71                }
72        }
73
74        bool call_once( once_flag & this, void (*func)(void) ) {
75                // is the call already done?
76                if(likely(once_state.READY == __atomic_load_n(&this.state, __ATOMIC_RELAXED))) {
77                        /* paranoid */ verify( is_poisoned(this.waiters) );
78                        return false;
79                }
80
81                // Try to CAS ourself as the thread that will actually call the function
82                int expected = ARMED;
83                if( __atomic_compare_exchange_n( &this.state, &expected, (int)once_state.IN_PROGRESS, false, __ATOMIC_SEQ_CST, __ATOMIC_SEQ_CST) ) {
84
85                        // we won the race, call the function
86                        once_call$( this, func );
87
88                        /* paranoid */ verify( is_poisoned(this.waiters) );
89                        /* paranoid */ verify( once_state.READY == __atomic_load_n(&this.state, __ATOMIC_RELAXED) );
90
91                        // in case someone cares, this call did do the underlying call
92                        return true;
93                }
94                else {
95
96                        // someone else is doing the call, just wait
97                        once_wait$( this );
98
99                        /* paranoid */ verify( is_poisoned(this.waiters) );
100                        /* paranoid */ verify( once_state.READY == __atomic_load_n(&this.state, __ATOMIC_RELAXED) );
101
102                        // in case someone cares, someone else did the call
103                        return false;
104                }
105        }
106}
Note: See TracBrowser for help on using the repository browser.