Chromium Code Reviews| OLD | NEW |
|---|---|
| 1 // Copyright 2016 The Chromium Authors. All rights reserved. | 1 // Copyright 2016 The Chromium Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
| 4 | 4 |
| 5 #include "mojo/edk/system/wait_set_dispatcher.h" | 5 #include "mojo/edk/system/wait_set_dispatcher.h" |
| 6 | 6 |
| 7 #include <utility> | 7 #include <utility> |
| 8 | 8 |
| 9 #include "base/logging.h" | 9 #include "base/logging.h" |
| 10 #include "mojo/edk/system/configuration.h" | 10 #include "mojo/edk/system/configuration.h" |
| 11 #include "mojo/edk/system/options_validation.h" | 11 #include "mojo/edk/system/options_validation.h" |
| 12 | 12 |
| 13 using mojo::util::MutexLocker; | 13 using mojo::util::MutexLocker; |
| 14 using mojo::util::RefPtr; | 14 using mojo::util::RefPtr; |
| 15 | 15 |
| 16 namespace mojo { | 16 namespace mojo { |
| 17 namespace system { | 17 namespace system { |
| 18 | 18 |
| 19 WaitSetDispatcher::Entry::Entry(MojoHandleSignals signals, uint64_t cookie) | 19 WaitSetDispatcher::Entry::Entry(RefPtr<Dispatcher>&& dispatcher, |
| 20 : signals(signals), cookie(cookie) {} | 20 MojoHandleSignals signals, |
| 21 uint64_t cookie) | |
| 22 : dispatcher(std::move(dispatcher)), signals(signals), cookie(cookie) {} | |
| 21 | 23 |
| 22 WaitSetDispatcher::Entry::~Entry() {} | 24 WaitSetDispatcher::Entry::~Entry() {} |
| 23 | 25 |
| 24 // static | 26 // static |
| 25 constexpr MojoHandleRights WaitSetDispatcher::kDefaultHandleRights; | 27 constexpr MojoHandleRights WaitSetDispatcher::kDefaultHandleRights; |
| 26 | 28 |
| 27 // static | 29 // static |
| 28 const MojoCreateWaitSetOptions WaitSetDispatcher::kDefaultCreateOptions = { | 30 const MojoCreateWaitSetOptions WaitSetDispatcher::kDefaultCreateOptions = { |
| 29 static_cast<uint32_t>(sizeof(MojoCreateWaitSetOptions)), | 31 static_cast<uint32_t>(sizeof(MojoCreateWaitSetOptions)), |
| 30 MOJO_CREATE_WAIT_SET_OPTIONS_FLAG_NONE}; | 32 MOJO_CREATE_WAIT_SET_OPTIONS_FLAG_NONE}; |
| (...skipping 69 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 100 | 102 |
| 101 WaitSetDispatcher::WaitSetDispatcher() {} | 103 WaitSetDispatcher::WaitSetDispatcher() {} |
| 102 | 104 |
| 103 WaitSetDispatcher::~WaitSetDispatcher() {} | 105 WaitSetDispatcher::~WaitSetDispatcher() {} |
| 104 | 106 |
| 105 void WaitSetDispatcher::CloseImplNoLock() { | 107 void WaitSetDispatcher::CloseImplNoLock() { |
| 106 mutex().AssertHeld(); | 108 mutex().AssertHeld(); |
| 107 | 109 |
| 108 CookieToEntryMap entries; | 110 CookieToEntryMap entries; |
| 109 std::swap(entries_, entries); | 111 std::swap(entries_, entries); |
| 110 possibly_triggered_head_ = nullptr; | 112 triggered_head_ = nullptr; |
| 111 possibly_triggered_tail_ = nullptr; | 113 triggered_tail_ = nullptr; |
| 112 possibly_triggered_count_ = 0u; | 114 triggered_count_ = 0u; |
| 113 | 115 |
| 114 // We want to remove the awakables outside the lock, so we have to unlock | 116 // We want to remove the awakables outside the lock, so we have to unlock |
| 115 // |mutex()|. Note that while unlocked, |Awake()| may get called. | 117 // |mutex()|. Note that while unlocked, |Awake()| may get called. |
| 116 // TODO(vtl): This is pretty terrible, but changing it would require pretty | 118 // TODO(vtl): This is pretty terrible, but changing it would require pretty |
| 117 // invasive changes in many other places. We really count on |Dispatcher| not | 119 // invasive changes in many other places. We really count on |Dispatcher| not |
| 118 // doing anything interesting after calling |CloseImplNoLock()|, and since | 120 // doing anything interesting after calling |CloseImplNoLock()|, and since |
| 119 // |CloseImplNoLock()| is allowed to do nothing all the lock invariants are | 121 // |CloseImplNoLock()| is allowed to do nothing all the lock invariants are |
| 120 // satisfied. | 122 // satisfied. |
| 121 DCHECK(is_closed_no_lock()); | 123 DCHECK(is_closed_no_lock()); |
| 122 mutex().Unlock(); | 124 mutex().Unlock(); |
| (...skipping 16 matching lines...) Expand all Loading... | |
| 139 mutex().AssertHeld(); | 141 mutex().AssertHeld(); |
| 140 NOTREACHED(); | 142 NOTREACHED(); |
| 141 return nullptr; | 143 return nullptr; |
| 142 } | 144 } |
| 143 | 145 |
| 144 MojoResult WaitSetDispatcher::WaitSetAddImpl( | 146 MojoResult WaitSetDispatcher::WaitSetAddImpl( |
| 145 UserPointer<const MojoWaitSetAddOptions> options, | 147 UserPointer<const MojoWaitSetAddOptions> options, |
| 146 RefPtr<Dispatcher>&& dispatcher, | 148 RefPtr<Dispatcher>&& dispatcher, |
| 147 MojoHandleSignals signals, | 149 MojoHandleSignals signals, |
| 148 uint64_t cookie) { | 150 uint64_t cookie) { |
| 151 // This will be owned by |entries_|, so it should only be used under | |
| 152 // |mutex()|. | |
| 149 Entry* entry = nullptr; | 153 Entry* entry = nullptr; |
| 150 { | 154 { |
| 151 MutexLocker locker(&mutex()); | 155 MutexLocker locker(&mutex()); |
| 152 if (is_closed_no_lock()) | 156 if (is_closed_no_lock()) |
| 153 return MOJO_RESULT_INVALID_ARGUMENT; | 157 return MOJO_RESULT_INVALID_ARGUMENT; |
| 154 if (is_busy_) | |
| 155 return MOJO_RESULT_BUSY; | |
| 156 MojoWaitSetAddOptions validated_options; | 158 MojoWaitSetAddOptions validated_options; |
| 157 MojoResult result = ValidateWaitSetAddOptions(options, &validated_options); | 159 MojoResult result = ValidateWaitSetAddOptions(options, &validated_options); |
| 158 if (result != MOJO_RESULT_OK) | 160 if (result != MOJO_RESULT_OK) |
| 159 return result; | 161 return result; |
| 160 if (entries_.find(cookie) != entries_.end()) | 162 if (entries_.find(cookie) != entries_.end()) |
| 161 return MOJO_RESULT_ALREADY_EXISTS; | 163 return MOJO_RESULT_ALREADY_EXISTS; |
| 162 if (entries_.size() >= GetConfiguration().max_wait_set_num_entries) | 164 if (entries_.size() >= GetConfiguration().max_wait_set_num_entries) |
| 163 return MOJO_RESULT_RESOURCE_EXHAUSTED; | 165 return MOJO_RESULT_RESOURCE_EXHAUSTED; |
| 164 // Note: We'll have to set the entry's dispatcher later. | 166 entry = new Entry(dispatcher.Clone(), signals, cookie); |
| 165 entry = new Entry(signals, cookie); | |
| 166 entries_[cookie] = std::unique_ptr<Entry>(entry); | 167 entries_[cookie] = std::unique_ptr<Entry>(entry); |
| 167 | |
| 168 is_busy_ = true; | |
| 169 } | 168 } |
| 170 | 169 |
| 171 HandleSignalsState signals_state; | 170 // Note: We can only call into another dispatcher outside the lock. This means |
| 171 // that *we* may be closed at any time! | |
| 172 | |
| 173 // We add ourself as a persistent awakable, which means that even if the | |
| 174 // condition is already satisfied or never-satisfiable we'll still be added. | |
| 172 MojoResult result = | 175 MojoResult result = |
| 173 dispatcher->AddAwakable(this, cookie, true, signals, &signals_state); | 176 dispatcher->AddAwakable(this, cookie, true, signals, nullptr); |
| 174 | 177 |
| 175 // Can't use |MutexLocker|, since we need to do some work outside the lock | 178 MutexLocker locker(&mutex()); |
| 176 // in some code paths. | |
| 177 mutex().Lock(); | |
| 178 DCHECK(is_busy_); | |
| 179 is_busy_ = false; | |
| 180 | 179 |
| 181 // Note: We may have been closed while |mutex()| was unlocked, so we have to | 180 // Everywhere below, if |entry| is used, its validity must be justified. E.g., |
| 182 // check again! | 181 // us being closed invalidates |entry|. |
| 182 | |
| 183 if (is_closed_no_lock()) { | 183 if (is_closed_no_lock()) { |
| 184 // Warning: In this case, |entry| has been invalidated, since it was owned | 184 // WARNING: |entry| is invalid here. |
| 185 // by |entries_|. | 185 |
| 186 DCHECK(entries_.empty()); | 186 // We need to remove ourself from the target dispatcher's awakable list. |
| 187 mutex().Unlock(); | 187 // Regardless of |result|, it's OK to just call |RemoveAwakable()|. |
| 188 if (result == MOJO_RESULT_OK || result == MOJO_RESULT_ALREADY_EXISTS) { | 188 dispatcher->RemoveAwakable(true, this, cookie, nullptr); |
| 189 // We have to remove ourself from the target dispatcher's awakable list. | 189 |
| 190 dispatcher->RemoveAwakable(true, this, cookie, nullptr); | |
| 191 } | |
| 192 return MOJO_RESULT_INVALID_ARGUMENT; | 190 return MOJO_RESULT_INVALID_ARGUMENT; |
| 193 } | 191 } |
| 194 | 192 |
| 195 DCHECK(entries_.find(cookie) != entries_.end()); | 193 // |entry| is valid: Since we weren't closed and the entry is still not marked |
| 194 // as ready, nothing should have removed it from |entries_|. | |
| 196 DCHECK_EQ(entries_[cookie].get(), entry); | 195 DCHECK_EQ(entries_[cookie].get(), entry); |
| 197 | 196 |
| 198 if (result == MOJO_RESULT_ALREADY_EXISTS) { | 197 if (result == MOJO_RESULT_INVALID_ARGUMENT) { |
| 199 // It was added, but the wait condition is already satisfied. | 198 // The target dispatcher was closed, so we weren't added to its awakable |
| 200 AddPossiblyTriggeredNoLock(entry, Entry::TriggerState::SATISFIED); | 199 // list. This means that user code has a race condition in this case. We |
| 201 } else if (result == MOJO_RESULT_FAILED_PRECONDITION) { | 200 // could try to keep the entry around and signal it as "cancelled", but it's |
| 202 AddPossiblyTriggeredNoLock(entry, Entry::TriggerState::UNSATISFIABLE); | 201 // simpler to just say that the target dispatcher was bad in the first |
| 203 } else if (result != MOJO_RESULT_OK) { | 202 // place. |
| 204 size_t num_erased = entries_.erase(cookie); | 203 entries_.erase(cookie); |
| 205 DCHECK_EQ(num_erased, 1u); | 204 return MOJO_RESULT_INVALID_ARGUMENT; |
| 206 mutex().Unlock(); | |
| 207 return result; | |
| 208 } | 205 } |
| 209 | 206 |
| 210 // Update the entry to actually have the dispatcher. | 207 // In all other cases, we were added. |
| 211 entry->dispatcher = std::move(dispatcher); | 208 DCHECK(result == MOJO_RESULT_OK || result == MOJO_RESULT_ALREADY_EXISTS || |
| 209 result == MOJO_RESULT_FAILED_PRECONDITION); | |
| 212 | 210 |
| 213 mutex().Unlock(); | 211 DCHECK(!entry->ready); |
| 212 entry->ready = true; | |
| 213 | |
| 214 return MOJO_RESULT_OK; | 214 return MOJO_RESULT_OK; |
| 215 } | 215 } |
| 216 | 216 |
| 217 MojoResult WaitSetDispatcher::WaitSetRemoveImpl(uint64_t cookie) { | 217 MojoResult WaitSetDispatcher::WaitSetRemoveImpl(uint64_t cookie) { |
| 218 RefPtr<Dispatcher> dispatcher; | 218 RefPtr<Dispatcher> dispatcher; |
| 219 { | 219 { |
| 220 MutexLocker locker(&mutex()); | 220 MutexLocker locker(&mutex()); |
| 221 if (is_closed_no_lock()) | 221 if (is_closed_no_lock()) |
| 222 return MOJO_RESULT_INVALID_ARGUMENT; | 222 return MOJO_RESULT_INVALID_ARGUMENT; |
| 223 if (is_busy_) | |
| 224 return MOJO_RESULT_BUSY; | |
| 225 auto it = entries_.find(cookie); | 223 auto it = entries_.find(cookie); |
| 226 if (it == entries_.end()) | 224 if (it == entries_.end()) |
| 227 return MOJO_RESULT_NOT_FOUND; | 225 return MOJO_RESULT_NOT_FOUND; |
| 228 | 226 |
| 229 Entry* entry = it->second.get(); | 227 Entry* entry = it->second.get(); |
| 228 if (!entry->ready) { | |
| 229 // |WaitSetAddImpl()| isn't done yet so, as far as user code is concerned, | |
| 230 // the entry with this cookie hasn't been added yet. | |
| 231 return MOJO_RESULT_NOT_FOUND; | |
| 232 } | |
| 233 | |
| 230 // We'll remove ourself from the target dispatcher's awakable list outside | 234 // We'll remove ourself from the target dispatcher's awakable list outside |
| 231 // the lock. | 235 // the lock. |
| 232 dispatcher = std::move(entry->dispatcher); | 236 dispatcher = std::move(entry->dispatcher); |
| 233 | 237 |
| 234 if (entry->trigger_state != Entry::TriggerState::NOT_TRIGGERED) | 238 if (entry->is_triggered) |
| 235 RemovePossiblyTriggeredNoLock(entry); | 239 RemoveTriggeredNoLock(entry); |
| 236 | 240 |
| 237 // Note: This invalidates |entry|. | 241 // Note: This invalidates |entry|. |
| 238 entries_.erase(it); | 242 entries_.erase(it); |
| 239 } | 243 } |
| 240 | 244 |
| 241 if (dispatcher) | 245 if (dispatcher) |
| 242 dispatcher->RemoveAwakable(true, this, cookie, nullptr); | 246 dispatcher->RemoveAwakable(true, this, cookie, nullptr); |
| 243 return MOJO_RESULT_OK; | 247 return MOJO_RESULT_OK; |
| 244 } | 248 } |
| 245 | 249 |
| (...skipping 19 matching lines...) Expand all Loading... | |
| 265 if (is_closed_no_lock()) { | 269 if (is_closed_no_lock()) { |
| 266 // See |CloseImplNoLock()|: This case may occur while we're unlocked in | 270 // See |CloseImplNoLock()|: This case may occur while we're unlocked in |
| 267 // |CloseImplNoLock()| (after that, we will have been removed from all the | 271 // |CloseImplNoLock()| (after that, we will have been removed from all the |
| 268 // awakable lists, so |Awake()| should no longer be called). | 272 // awakable lists, so |Awake()| should no longer be called). |
| 269 return; | 273 return; |
| 270 } | 274 } |
| 271 | 275 |
| 272 auto it = entries_.find(context); | 276 auto it = entries_.find(context); |
| 273 DCHECK(it != entries_.end()); | 277 DCHECK(it != entries_.end()); |
| 274 const auto& entry = it->second; | 278 const auto& entry = it->second; |
| 275 // We should only ever get at most one "closed" (cancelled). | 279 // Once we get "cancelled", we should never be awoken again. |
| 276 DCHECK_NE(static_cast<int>(entry->trigger_state), | 280 DCHECK(entry->dispatcher); |
| 277 static_cast<int>(Entry::TriggerState::CLOSED)); | |
| 278 switch (reason) { | 281 switch (reason) { |
| 279 case AwakeReason::CANCELLED: | 282 case AwakeReason::CANCELLED: |
| 280 if (entry->trigger_state == Entry::TriggerState::NOT_TRIGGERED) { | 283 if (!entry->is_triggered) |
| 281 AddPossiblyTriggeredNoLock(entry.get(), Entry::TriggerState::CLOSED); | 284 AddTriggeredNoLock(entry.get()); |
| 282 } else { | |
| 283 entry->trigger_state = Entry::TriggerState::CLOSED; | |
| 284 } | |
| 285 entry->dispatcher = nullptr; | 285 entry->dispatcher = nullptr; |
| 286 break; | 286 break; |
| 287 case AwakeReason::SATISFIED: | 287 case AwakeReason::SATISFIED: |
| 288 case AwakeReason::UNSATISFIABLE: | 288 case AwakeReason::UNSATISFIABLE: |
| 289 // We shouldn't see these since we're used as a persistent |Awakable|. | 289 // We shouldn't see these since we're used as a persistent |Awakable|. |
| 290 NOTREACHED(); | 290 NOTREACHED(); |
| 291 break; | 291 break; |
| 292 case AwakeReason::INITIALIZE: | 292 case AwakeReason::INITIALIZE: |
| 293 // TODO(vtl): Save the initial state here (and maybe add to the triggered | 293 DCHECK(entry->signals_state.equals(HandleSignalsState())); |
| 294 // list), and then update state on CHANGED. | 294 // Fall through. |
| 295 break; | |
| 296 case AwakeReason::CHANGED: | 295 case AwakeReason::CHANGED: |
| 297 if (signals_state.satisfies(entry->signals)) { | 296 // Record the state for anyone waiting. |
| 298 if (entry->trigger_state == Entry::TriggerState::NOT_TRIGGERED) { | 297 entry->signals_state = signals_state; |
| 299 AddPossiblyTriggeredNoLock(entry.get(), | 298 if (entry->signals_state.satisfies(entry->signals) || |
| 300 Entry::TriggerState::SATISFIED); | 299 !entry->signals_state.can_satisfy(entry->signals)) { |
|
vardhan
2016/06/23 18:26:11
is this right? if entry's signal state cannot sati
viettrungluu
2016/06/23 20:01:05
Yes, we want to report that it's become "never"-sa
| |
| 301 } else { | 300 if (!entry->is_triggered) |
| 302 entry->trigger_state = Entry::TriggerState::SATISFIED; | 301 AddTriggeredNoLock(entry.get()); |
| 303 } | |
| 304 } else if (!signals_state.can_satisfy(entry->signals)) { | |
| 305 if (entry->trigger_state == Entry::TriggerState::NOT_TRIGGERED) { | |
| 306 AddPossiblyTriggeredNoLock(entry.get(), | |
| 307 Entry::TriggerState::UNSATISFIABLE); | |
| 308 } else { | |
| 309 entry->trigger_state = Entry::TriggerState::UNSATISFIABLE; | |
| 310 } | |
| 311 } else { | 302 } else { |
| 312 if (entry->trigger_state != Entry::TriggerState::NOT_TRIGGERED) | 303 if (entry->is_triggered) |
| 313 RemovePossiblyTriggeredNoLock(entry.get()); | 304 RemoveTriggeredNoLock(entry.get()); |
| 314 } | 305 } |
| 315 break; | 306 break; |
| 316 } | 307 } |
| 317 } | 308 } |
| 318 | 309 |
| 319 void WaitSetDispatcher::AddPossiblyTriggeredNoLock( | 310 void WaitSetDispatcher::AddTriggeredNoLock(Entry* entry) { |
| 320 Entry* entry, | 311 DCHECK(!entry->is_triggered); |
| 321 Entry::TriggerState new_trigger_state) { | 312 DCHECK(!entry->triggered_previous); |
| 322 DCHECK_EQ(static_cast<int>(entry->trigger_state), | 313 DCHECK(!entry->triggered_next); |
| 323 static_cast<int>(Entry::TriggerState::NOT_TRIGGERED)); | |
| 324 DCHECK(!entry->possibly_triggered_previous); | |
| 325 DCHECK(!entry->possibly_triggered_next); | |
| 326 DCHECK_NE(static_cast<int>(new_trigger_state), | |
| 327 static_cast<int>(Entry::TriggerState::NOT_TRIGGERED)); | |
| 328 | 314 |
| 329 entry->trigger_state = new_trigger_state; | 315 entry->is_triggered = true; |
| 330 possibly_triggered_count_++; | 316 triggered_count_++; |
| 331 | 317 |
| 332 if (!possibly_triggered_tail_) { | 318 if (!triggered_tail_) { |
| 333 DCHECK(!possibly_triggered_head_); | 319 DCHECK(!triggered_head_); |
| 334 possibly_triggered_head_ = entry; | 320 triggered_head_ = entry; |
| 335 possibly_triggered_tail_ = entry; | 321 triggered_tail_ = entry; |
| 336 return; | 322 return; |
| 337 } | 323 } |
| 338 | 324 |
| 339 Entry* old_tail = possibly_triggered_tail_; | 325 Entry* old_tail = triggered_tail_; |
| 340 entry->possibly_triggered_previous = old_tail; | 326 entry->triggered_previous = old_tail; |
| 341 DCHECK(!old_tail->possibly_triggered_next); | 327 DCHECK(!old_tail->triggered_next); |
| 342 old_tail->possibly_triggered_next = entry; | 328 old_tail->triggered_next = entry; |
| 343 possibly_triggered_tail_ = entry; | 329 triggered_tail_ = entry; |
| 344 } | 330 } |
| 345 | 331 |
| 346 void WaitSetDispatcher::RemovePossiblyTriggeredNoLock(Entry* entry) { | 332 void WaitSetDispatcher::RemoveTriggeredNoLock(Entry* entry) { |
| 347 DCHECK_NE(static_cast<int>(entry->trigger_state), | 333 DCHECK(entry->is_triggered); |
| 348 static_cast<int>(Entry::TriggerState::NOT_TRIGGERED)); | 334 entry->is_triggered = false; |
| 349 entry->trigger_state = Entry::TriggerState::NOT_TRIGGERED; | 335 triggered_count_--; |
| 350 possibly_triggered_count_--; | |
| 351 | 336 |
| 352 if (!entry->possibly_triggered_previous) { | 337 if (!entry->triggered_previous) { |
| 353 DCHECK_EQ(entry, possibly_triggered_head_); | 338 DCHECK_EQ(entry, triggered_head_); |
| 354 possibly_triggered_head_ = entry->possibly_triggered_next; | 339 triggered_head_ = entry->triggered_next; |
| 355 } else { | 340 } else { |
| 356 entry->possibly_triggered_previous->possibly_triggered_next = | 341 entry->triggered_previous->triggered_next = entry->triggered_next; |
| 357 entry->possibly_triggered_next; | |
| 358 } | 342 } |
| 359 | 343 |
| 360 if (!entry->possibly_triggered_next) { | 344 if (!entry->triggered_next) { |
| 361 DCHECK_EQ(entry, possibly_triggered_tail_); | 345 DCHECK_EQ(entry, triggered_tail_); |
| 362 possibly_triggered_tail_ = entry->possibly_triggered_previous; | 346 triggered_tail_ = entry->triggered_previous; |
| 363 } else { | 347 } else { |
| 364 entry->possibly_triggered_next->possibly_triggered_previous = | 348 entry->triggered_next->triggered_previous = entry->triggered_previous; |
| 365 entry->possibly_triggered_previous; | |
| 366 } | 349 } |
| 367 | 350 |
| 368 entry->possibly_triggered_previous = nullptr; | 351 entry->triggered_previous = nullptr; |
| 369 entry->possibly_triggered_next = nullptr; | 352 entry->triggered_next = nullptr; |
| 370 } | 353 } |
| 371 | 354 |
| 372 } // namespace system | 355 } // namespace system |
| 373 } // namespace mojo | 356 } // namespace mojo |
| OLD | NEW |