1.1 --- a/libfsclient/lib/src/notifier.cc Sat Aug 14 18:41:57 2021 +0200
1.2 +++ b/libfsclient/lib/src/notifier.cc Tue Aug 17 23:44:36 2021 +0200
1.3 @@ -33,93 +33,88 @@
1.4
1.5
1.6
1.7 -/* Thread-local storage workaround. */
1.8 +/* Null notification state. */
1.9 +
1.10 +static FileNotificationState _null_state;
1.11 +
1.12 +
1.13 +
1.14 +/* Lock protecting per-task notifier access. */
1.15 +
1.16 +static std::mutex _lock;
1.17 +
1.18 +/* Per-task storage for specific waiting operations. */
1.19 +
1.20 +static SpecificFileNotifier *_notifier = NULL;
1.21 +
1.22 +/* Per-thread storage for "open" waiting operations.
1.23 + (This workaround for thread-local storage maps thread capabilities to
1.24 + notifiers). */
1.25 +
1.26 +static std::map<l4_cap_idx_t, GeneralFileNotifier *> _notifiers;
1.27 +
1.28 +
1.29
1.30 -static std::mutex lock;
1.31 -static std::map<l4_cap_idx_t, FileNotifier *> notifiers;
1.32 +/* Return the per-task notifier for file-specific waiting operations. */
1.33 +
1.34 +SpecificFileNotifier *get_task_notifier()
1.35 +{
1.36 + std::lock_guard<std::mutex> guard(_lock);
1.37 +
1.38 + /* Start any new notifier. */
1.39
1.40 -FileNotifier *get_notifier()
1.41 + if (_notifier == NULL)
1.42 + {
1.43 + _notifier = new SpecificFileNotifier;
1.44 + _notifier->start();
1.45 + }
1.46 +
1.47 + return _notifier;
1.48 +}
1.49 +
1.50 +/* Return the per-thread notifier for general file waiting operations. */
1.51 +
1.52 +GeneralFileNotifier *get_thread_notifier()
1.53 {
1.54 - std::lock_guard<std::mutex> guard(lock);
1.55 + std::lock_guard<std::mutex> guard(_lock);
1.56
1.57 l4_cap_idx_t thread = pthread_l4_cap(pthread_self());
1.58 - FileNotifier *notifier = notifiers[thread];
1.59 + GeneralFileNotifier *notifier = _notifiers[thread];
1.60
1.61 /* Start any new notifier. */
1.62
1.63 if (notifier == NULL)
1.64 {
1.65 - notifier = new FileNotifier;
1.66 - notifiers[thread] = notifier;
1.67 + notifier = new GeneralFileNotifier;
1.68 + _notifiers[thread] = notifier;
1.69 notifier->start();
1.70 }
1.71
1.72 return notifier;
1.73 }
1.74
1.75 -
1.76 -
1.77 -/* Subscribe to notification events on a file. */
1.78 +/* Helper function to obtain the appropriate notifier. */
1.79
1.80 -long FileNotifier::subscribe(file_t *file, notify_flags_t flags)
1.81 +FileNotifier *get_notifier(notifier_t notifier_type)
1.82 {
1.83 - /* Create a notification endpoint, if necessary. */
1.84 -
1.85 - if (l4_is_invalid_cap(file->notifier))
1.86 + switch (notifier_type)
1.87 {
1.88 - long err = ipc_server_new_for_thread(&file->notifier, file, _thread);
1.89 -
1.90 - if (err)
1.91 - return err;
1.92 + case NOTIFIER_TASK: return get_task_notifier();
1.93 + case NOTIFIER_THREAD: return get_thread_notifier();
1.94 + default: return NULL;
1.95 }
1.96 -
1.97 - client_Notification notify(file->ref);
1.98 -
1.99 - return notify.subscribe(file->notifier, flags);
1.100 }
1.101
1.102 -/* Unsubscribe from notification events on a file. */
1.103
1.104 -long FileNotifier::unsubscribe(file_t *file)
1.105 -{
1.106 - if (l4_is_invalid_cap(file->notifier))
1.107 - return -L4_EINVAL;
1.108 -
1.109 - client_Notification notify(file->ref);
1.110
1.111 - long err = notify.unsubscribe(file->notifier);
1.112 -
1.113 - if (err)
1.114 - return err;
1.115 -
1.116 - ipc_cap_free_um(file->notifier);
1.117 - return L4_EOK;
1.118 -}
1.119 +/* Invoke the mainloop in a thread. */
1.120
1.121 -/* Handle a notification event for a file. Ideally, this would be invoked by the
1.122 - generic server dispatch mechanism, with the gate label being interpreted and
1.123 - provided as the first parameter. */
1.124 -
1.125 -void FileNotifier::_notify(file_t *file, notify_flags_t flags)
1.126 +static void *notifier_mainloop(void *data)
1.127 {
1.128 - std::unique_lock<std::mutex> guard(_lock);
1.129 -
1.130 - /* Record the flags for the file object. Where no flags are already recorded,
1.131 - the new flags will be recorded and the file object queued. Otherwise, the
1.132 - new flags will be combined with the recorded flags. */
1.133 + FileNotifier *notifier = reinterpret_cast<FileNotifier *>(data);
1.134
1.135 - notify_flags_t recorded = _affected_flags[file];
1.136 -
1.137 - _affected_flags[file] = recorded | flags;
1.138 -
1.139 - /* Add a file queue entry for any files without recorded notifications. */
1.140 -
1.141 - if (!recorded)
1.142 - _affected.push_back(file);
1.143 -
1.144 - /* Notify any waiting caller. */
1.145 -
1.146 - _notified.notify_one();
1.147 + notifier->mainloop();
1.148 + return 0;
1.149 }
1.150
1.151 /* Listen for notifications. */
1.152 @@ -165,16 +160,6 @@
1.153 ipc_message_free(&msg);
1.154 }
1.155
1.156 -/* Invoke the mainloop in a thread. */
1.157 -
1.158 -static void *notifier_mainloop(void *data)
1.159 -{
1.160 - FileNotifier *notifier = reinterpret_cast<FileNotifier *>(data);
1.161 -
1.162 - notifier->mainloop();
1.163 - return 0;
1.164 -}
1.165 -
1.166 /* Start listening for notifications. */
1.167
1.168 long FileNotifier::start()
1.169 @@ -199,36 +184,238 @@
1.170 return L4_EOK;
1.171 }
1.172
1.173 +
1.174 +
1.175 +/* Return a notification state object for the given file or a null object if no
1.176 + record existed for the file. */
1.177 +
1.178 +FileNotificationState &FileNotifier::file_state(file_t *file, bool create)
1.179 +{
1.180 + FileNotificationStates::iterator it = _state.find(file);
1.181 +
1.182 + if (it == _state.end())
1.183 + {
1.184 + if (create)
1.185 + return _state[file];
1.186 + else
1.187 + return _null_state;
1.188 + }
1.189 +
1.190 + return it->second;
1.191 +}
1.192 +
1.193 +/* Subscribe to notification events on a file. */
1.194 +
1.195 +long FileNotifier::subscribe(file_t *file, notify_flags_t flags)
1.196 +{
1.197 + /* Acquire the lock for state lookup. */
1.198 +
1.199 + std::unique_lock<std::mutex> state_guard(_state_lock);
1.200 +
1.201 + FileNotificationState &state = file_state(file, true);
1.202 +
1.203 + /* Create a notification endpoint, if necessary. */
1.204 +
1.205 + if (state.is_null())
1.206 + {
1.207 + long err = ipc_server_new_for_thread(&state.endpoint, file, _thread);
1.208 +
1.209 + if (err)
1.210 + return err;
1.211 + }
1.212 +
1.213 + client_Notification notify(file->ref);
1.214 +
1.215 + return notify.subscribe(state.endpoint, flags);
1.216 +}
1.217 +
1.218 +/* Unsubscribe from notification events on a file. */
1.219 +
1.220 +long FileNotifier::unsubscribe(file_t *file)
1.221 +{
1.222 + /* Acquire the lock for state lookup. */
1.223 +
1.224 + std::unique_lock<std::mutex> state_guard(_state_lock);
1.225 +
1.226 + FileNotificationState &state = file_state(file, false);
1.227 +
1.228 + if (state.is_null())
1.229 + return -L4_EINVAL;
1.230 +
1.231 + client_Notification notify(file->ref);
1.232 +
1.233 + long err = notify.unsubscribe(state.endpoint);
1.234 +
1.235 + if (err)
1.236 + return err;
1.237 +
1.238 + _unsubscribe(state, file);
1.239 +
1.240 + /* Remove the lock for updating file state. */
1.241 +
1.242 + _file_locks.erase(file);
1.243 +
1.244 + return L4_EOK;
1.245 +}
1.246 +
1.247 +/* Remove file notification state from the notifier. */
1.248 +
1.249 +void FileNotifier::_unsubscribe(FileNotificationState &state, file_t *file)
1.250 +{
1.251 + /* Acquire the lock for updating file state. */
1.252 +
1.253 + std::mutex &file_lock = _file_locks[file];
1.254 + std::unique_lock<std::mutex> file_guard(file_lock);
1.255 +
1.256 + /* Remove file-specific state. */
1.257 +
1.258 + ipc_cap_free_um(state.endpoint);
1.259 + _state.erase(file);
1.260 +}
1.261 +
1.262 +
1.263 +
1.264 +/* Handle a notification event for a file. Ideally, this would be invoked by the
1.265 + generic server dispatch mechanism, with the gate label being interpreted and
1.266 + provided as the first parameter. */
1.267 +
1.268 +void GeneralFileNotifier::_notify(file_t *file, notify_flags_t flags)
1.269 +{
1.270 + /* Enter critical section for the notifier (affecting all files). */
1.271 +
1.272 + std::unique_lock<std::mutex> general_guard(_general_lock);
1.273 +
1.274 + /* Acquire the lock for state lookup. */
1.275 +
1.276 + std::unique_lock<std::mutex> state_guard(_state_lock);
1.277 +
1.278 + FileNotificationState &state = file_state(file, false);
1.279 +
1.280 + if (state.is_null())
1.281 + return;
1.282 +
1.283 + /* Acquire the lock for the file state itself. */
1.284 +
1.285 + std::unique_lock<std::mutex> file_guard(state.lock);
1.286 +
1.287 + /* Record flags and return previous flags. */
1.288 +
1.289 + notify_flags_t recorded = state.pending;
1.290 +
1.291 + state.pending |= flags;
1.292 +
1.293 + /* Add a file queue entry for any files without previous notifications. */
1.294 +
1.295 + if (!recorded)
1.296 + _affected.push_back(file);
1.297 +
1.298 + /* Notify any waiting caller. */
1.299 +
1.300 + _general_condition.notify_one();
1.301 +}
1.302 +
1.303 +void SpecificFileNotifier::_notify(file_t *file, notify_flags_t flags)
1.304 +{
1.305 + /* Acquire the lock for state lookup. */
1.306 +
1.307 + std::unique_lock<std::mutex> state_guard(_state_lock);
1.308 +
1.309 + FileNotificationState &state = file_state(file, false);
1.310 +
1.311 + if (state.is_null())
1.312 + return;
1.313 +
1.314 + /* Acquire the lock for the file state itself. */
1.315 +
1.316 + std::unique_lock<std::mutex> file_guard(state.lock);
1.317 +
1.318 + state.pending |= flags;
1.319 +
1.320 + /* Notify any waiting caller. */
1.321 +
1.322 + state.condition.notify_one();
1.323 +}
1.324 +
1.325 +
1.326 +
1.327 +/* Transfer pending notifications to the given file. This must be called with a
1.328 + lock acquired on the file notification state. */
1.329 +
1.330 +bool FileNotifier::_transfer(FileNotificationState &state, file_t *file)
1.331 +{
1.332 + notify_flags_t recorded = state.pending;
1.333 +
1.334 + if (recorded)
1.335 + {
1.336 + file->notifications = recorded;
1.337 + state.pending = 0;
1.338 + return true;
1.339 + }
1.340 +
1.341 + return false;
1.342 +}
1.343 +
1.344 +
1.345 +
1.346 +/* Obtain file state and transfer notifications. */
1.347 +
1.348 +bool GeneralFileNotifier::_retrieve_for_file(file_t *file)
1.349 +{
1.350 + /* Acquire the lock for state lookup. */
1.351 +
1.352 + std::unique_lock<std::mutex> state_guard(_state_lock);
1.353 +
1.354 + FileNotificationState &state = file_state(file, false);
1.355 +
1.356 + if (state.is_null())
1.357 + return false;
1.358 +
1.359 + /* Acquire the lock for the file state itself, then release the state lock. */
1.360 +
1.361 + std::unique_lock<std::mutex> file_guard(state.lock);
1.362 +
1.363 + state_guard.unlock();
1.364 +
1.365 + /* Call generic method to transfer notifications, if possible. */
1.366 +
1.367 + return _transfer(state, file);
1.368 +}
1.369 +
1.370 +/* Obtain queued files until one is found that still has events recorded for it.
1.371 + This must be called with the notifier's general lock acquired. */
1.372 +
1.373 +bool GeneralFileNotifier::_retrieve(file_t **file)
1.374 +{
1.375 + while (!_affected.empty())
1.376 + {
1.377 + *file = _affected.front();
1.378 + _affected.pop_front();
1.379 +
1.380 + if (_retrieve_for_file(*file))
1.381 + return true;
1.382 + }
1.383 +
1.384 + return false;
1.385 +}
1.386 +
1.387 +
1.388 +
1.389 /* Wait for notification events on files. */
1.390
1.391 -long FileNotifier::wait(file_t **file)
1.392 +long GeneralFileNotifier::wait(file_t **file)
1.393 {
1.394 - std::unique_lock<std::mutex> guard(_lock);
1.395 + std::unique_lock<std::mutex> general_guard(_general_lock);
1.396
1.397 while (1)
1.398 {
1.399 - /* Obtain queued files until one is found that still has events recorded
1.400 - for it. (Waiting for events specific to one file will remove recorded
1.401 - events but not any file queue entries.) */
1.402 -
1.403 - while (!_affected.empty())
1.404 - {
1.405 - *file = _affected.front();
1.406 - _affected.pop_front();
1.407 -
1.408 - notify_flags_t recorded = _affected_flags[*file];
1.409 + /* With pending notifications, update the first file and exit. */
1.410
1.411 - if (recorded)
1.412 - {
1.413 - (*file)->notifications = recorded;
1.414 - _affected_flags.erase(*file);
1.415 - return L4_EOK;
1.416 - }
1.417 - }
1.418 + if (_retrieve(file))
1.419 + break;
1.420
1.421 - /* No queued events. */
1.422 + /* Otherwise, wait for notifications. */
1.423
1.424 - _notified.wait(guard);
1.425 + _general_condition.wait(general_guard);
1.426 }
1.427
1.428 return L4_EOK;
1.429 @@ -236,24 +423,33 @@
1.430
1.431 /* Wait for notifications from a single file. */
1.432
1.433 -long FileNotifier::wait_file(file_t *file)
1.434 +long SpecificFileNotifier::wait_file(file_t *file)
1.435 {
1.436 - std::unique_lock<std::mutex> guard(_lock);
1.437 + /* Acquire the lock for reading file state. */
1.438 +
1.439 + std::unique_lock<std::mutex> state_guard(_state_lock);
1.440 +
1.441 + FileNotificationState &state = file_state(file, false);
1.442 +
1.443 + if (state.is_null())
1.444 + return -L4_EINVAL;
1.445 +
1.446 + /* Acquire the lock for the file state itself, then release the state lock. */
1.447 +
1.448 + std::unique_lock<std::mutex> file_guard(state.lock);
1.449 +
1.450 + state_guard.unlock();
1.451
1.452 while (1)
1.453 {
1.454 - notify_flags_t recorded = _affected_flags[file];
1.455 + /* With pending notifications, update the file and exit. */
1.456
1.457 - if (recorded)
1.458 - {
1.459 - file->notifications = recorded;
1.460 - _affected_flags.erase(file);
1.461 - return L4_EOK;
1.462 - }
1.463 + if (_transfer(state, file))
1.464 + break;
1.465
1.466 - /* No recorded events for the file. */
1.467 + /* Otherwise, wait for notifications. */
1.468
1.469 - _notified.wait(guard);
1.470 + state.condition.wait(file_guard);
1.471 }
1.472
1.473 return L4_EOK;