1// Copyright 2017 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include <zircon/syscalls.h>
6#include <zircon/types.h>
7#include <lib/zx/timer.h>
8
9#include <dispatcher-pool/dispatcher-execution-domain.h>
10#include <dispatcher-pool/dispatcher-wakeup-event.h>
11
12namespace dispatcher {
13
14// static
15fbl::RefPtr<WakeupEvent> WakeupEvent::Create() {
16    fbl::AllocChecker ac;
17
18    auto ptr = new (&ac) WakeupEvent();
19    if (!ac.check())
20        return nullptr;
21
22    return fbl::AdoptRef(ptr);
23}
24
25zx_status_t WakeupEvent::Activate(fbl::RefPtr<ExecutionDomain> domain,
26                                  ProcessHandler process_handler) {
27    if (process_handler == nullptr)
28        return ZX_ERR_INVALID_ARGS;
29
30    fbl::AutoLock obj_lock(&obj_lock_);
31    zx::event event;
32    zx_status_t res = zx::event::create(0, &event);
33    if (res != ZX_OK)
34        return res;
35
36    res = ActivateLocked(fbl::move(event), fbl::move(domain));
37    if (res != ZX_OK)
38        return res;
39
40    res = WaitOnPortLocked();
41    if (res != ZX_OK) {
42        InternalDeactivateLocked();
43        return res;
44    }
45
46    process_handler_ = fbl::move(process_handler);
47
48    return ZX_OK;
49}
50
51void WakeupEvent::Deactivate() {
52    ProcessHandler old_process_handler;
53
54    {
55        fbl::AutoLock obj_lock(&obj_lock_);
56        InternalDeactivateLocked();
57
58        // If we were previously signalled, we are not any more.
59        signaled_ = false;
60
61        // If we are in the process of actively dispatching, do not discard our
62        // handler just yet.  It is currently being used by the dispatch thread.
63        // Instead, wait until the dispatch thread unwinds and allow it to clean
64        // up the handler.
65        //
66        // Otherwise, transfer the handler state into local storage and let it
67        // destruct after we have released the object lock.
68        if (dispatch_state() != DispatchState::Dispatching) {
69            ZX_DEBUG_ASSERT((dispatch_state() == DispatchState::Idle) ||
70                            (dispatch_state() == DispatchState::WaitingOnPort));
71            old_process_handler = fbl::move(process_handler_);
72        }
73    }
74}
75
76zx_status_t WakeupEvent::Signal() {
77    fbl::AutoLock obj_lock(&obj_lock_);
78
79
80    // If we are no longer active, we cannot signal the event.
81    if (!is_active())
82        return ZX_ERR_BAD_HANDLE;
83
84    // If we are still active, then our handle had better be valid.
85    ZX_DEBUG_ASSERT(handle_.is_valid());
86
87    // Update our internal bookkeeping.
88    signaled_ = true;
89
90    // If we have already fired and are in the process of dispatching, don't
91    // bother to actually signal the event at the kernel level.
92    if ((dispatch_state() == DispatchState::DispatchPending) ||
93        (dispatch_state() == DispatchState::Dispatching)) {
94        return ZX_OK;
95    }
96
97    zx_status_t res = zx_object_signal(handle_.get(), 0u, ZX_USER_SIGNAL_0);
98    ZX_DEBUG_ASSERT(res == ZX_OK);  // I cannot think of any reason that this should ever fail.
99
100    return res;
101}
102
103void WakeupEvent::Dispatch(ExecutionDomain* domain) {
104    ZX_DEBUG_ASSERT(domain != nullptr);
105    ZX_DEBUG_ASSERT(process_handler_ != nullptr);
106
107    {
108        // Clear the signalled flag.  Someone might signal us again during the
109        // dispatch operation.
110        fbl::AutoLock obj_lock(&obj_lock_);
111        ZX_DEBUG_ASSERT(dispatch_state() == DispatchState::Dispatching);
112        signaled_ = false;
113    }
114
115    zx_status_t res = process_handler_(this);
116    ProcessHandler old_process_handler;
117    {
118        fbl::AutoLock obj_lock(&obj_lock_);
119        ZX_DEBUG_ASSERT(dispatch_state() == DispatchState::Dispatching);
120        dispatch_state_ = DispatchState::Idle;
121
122        // Was there a problem during processing?  If so, make sure that we
123        // de-activate ourselves.
124        if (res != ZX_OK) {
125            InternalDeactivateLocked();
126        }
127
128        // Are we still active?  If so, either setup the next port wait
129        // operation, or re-queue ourselves if we were signalled during the
130        // dispatch operation.
131        if (is_active()) {
132            if (signaled_) {
133                dispatch_state_ = DispatchState::WaitingOnPort;
134                res = domain->AddPendingWork(this);
135            } else {
136                res = zx_object_signal(handle_.get(), ZX_USER_SIGNAL_0, 0u);
137                if (res == ZX_OK)
138                    res = WaitOnPortLocked();
139            }
140
141            if (res != ZX_OK) {
142                dispatch_state_ = DispatchState::Idle;
143                InternalDeactivateLocked();
144            }
145        }
146
147        // Have we become deactivated (either during dispatching or just now)?
148        // If so, move our process handler state outside of our lock so that it
149        // can safely destruct.
150        if (!is_active()) {
151            old_process_handler = fbl::move(process_handler_);
152        }
153    }
154}
155
156}  // namespace dispatcher
157