1 /* 2 * Copyright (C) 2016 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 #ifndef CHRE_CORE_EVENT_LOOP_H_ 18 #define CHRE_CORE_EVENT_LOOP_H_ 19 20 #include <pw_function/function.h> 21 #include <stddef.h> 22 #include <optional> 23 24 #include "chre/core/event.h" 25 #include "chre/core/nanoapp.h" 26 #include "chre/core/timer_pool.h" 27 #include "chre/platform/atomic.h" 28 #include "chre/platform/mutex.h" 29 #include "chre/platform/power_control_manager.h" 30 #include "chre/platform/system_time.h" 31 #include "chre/util/dynamic_vector.h" 32 #include "chre/util/non_copyable.h" 33 #include "chre/util/system/debug_dump.h" 34 #include "chre/util/system/message_common.h" 35 #include "chre/util/system/stats_container.h" 36 #include "chre/util/unique_ptr.h" 37 #include "chre_api/chre/event.h" 38 39 #ifdef CHRE_STATIC_EVENT_LOOP 40 #include "chre/util/system/fixed_size_blocking_queue.h" 41 #include "chre/util/system/synchronized_memory_pool.h" 42 43 // These default values can be overridden in the variant-specific makefile. 44 #ifndef CHRE_MAX_EVENT_COUNT 45 #define CHRE_MAX_EVENT_COUNT 96 46 #endif 47 48 #ifndef CHRE_MAX_UNSCHEDULED_EVENT_COUNT 49 #define CHRE_MAX_UNSCHEDULED_EVENT_COUNT 96 50 #endif 51 #else 52 #include "chre/util/blocking_segmented_queue.h" 53 #include "chre/util/system/synchronized_expandable_memory_pool.h" 54 55 // These default values can be overridden in the variant-specific makefile. 56 #ifndef CHRE_EVENT_PER_BLOCK 57 #define CHRE_EVENT_PER_BLOCK 24 58 #endif 59 60 #ifndef CHRE_MAX_EVENT_BLOCKS 61 #define CHRE_MAX_EVENT_BLOCKS 4 62 #endif 63 64 #endif 65 66 namespace chre { 67 68 /** 69 * The EventLoop represents a single thread of execution that is shared among 70 * zero or more nanoapps. As the name implies, the EventLoop is built around a 71 * loop that delivers events to the nanoapps managed within for processing. 72 */ 73 class EventLoop : public NonCopyable { 74 public: 75 /** 76 * Synchronous callback used with forEachNanoapp 77 */ 78 typedef void(NanoappCallbackFunction)(const Nanoapp *nanoapp, void *data); 79 EventLoop()80 EventLoop() 81 : 82 #ifndef CHRE_STATIC_EVENT_LOOP 83 mEvents(kMaxEventBlock), 84 #endif 85 mTimeLastWakeupBucketCycled(SystemTime::getMonotonicTime()), 86 mRunning(true) { 87 } 88 89 /** 90 * Searches the set of nanoapps managed by this EventLoop for one with the 91 * given app ID. If found, provides its instance ID, which can be used to send 92 * events to the app. 93 * 94 * This function is safe to call from any thread. 95 * 96 * @param appId The nanoapp identifier to search for. 97 * @param instanceId If this function returns true, will be populated with the 98 * instanceId associated with the given appId; otherwise unmodified. 99 * Must not be null. 100 * @return true if the given app ID was found and instanceId was populated 101 */ 102 bool findNanoappInstanceIdByAppId(uint64_t appId, uint16_t *instanceId) const; 103 104 /** 105 * Iterates over the list of Nanoapps managed by this EventLoop, and invokes 106 * the supplied callback for each one. This holds a lock if necessary, so it 107 * is safe to call from any thread. 108 * 109 * @param callback Function to invoke on each Nanoapp (synchronously) 110 * @param data Arbitrary data to pass to the callback 111 */ 112 void forEachNanoapp(NanoappCallbackFunction *callback, void *data); 113 114 /** 115 * Invokes a message to host free callback supplied by the given nanoapp 116 * (identified by app ID). Ensures that the calling context is updated 117 * appropriately. 118 * 119 * @param appId Identifies the nanoapp that sent this message and supplied the 120 * free callback 121 * @param freeFunction The non-null message free callback given by the nanoapp 122 * @param message Pointer to the message data 123 * @param messageSize Size of the message 124 */ 125 void invokeMessageFreeFunction(uint64_t appId, 126 chreMessageFreeFunction *freeFunction, 127 void *message, size_t messageSize); 128 129 /** 130 * Invokes the Nanoapp's start callback, and if successful, adds it to the 131 * set of Nanoapps managed by this EventLoop. This function must only be 132 * called from the context of the thread that runs this event loop (i.e. from 133 * the same thread that will call run() or from a callback invoked within 134 * run()). 135 * 136 * @param nanoapp The nanoapp that will be started. Upon success, this 137 * UniquePtr will become invalid, as the underlying Nanoapp instance 138 * will have been transferred to be managed by this EventLoop. 139 * @return true if the app was started successfully 140 */ 141 bool startNanoapp(UniquePtr<Nanoapp> &nanoapp); 142 143 /** 144 * Stops and unloads a nanoapp identified by its instance ID. The end entry 145 * point will be invoked, and the chre::Nanoapp instance will be destroyed. 146 * After this function returns, all references to the Nanoapp instance are 147 * invalidated. 148 * 149 * @param instanceId The nanoapp's unique instance identifier 150 * @param allowSystemNanoappUnload If false, this function will reject 151 * attempts to unload a system nanoapp 152 * @param nanoappStarted Indicates whether the nanoapp successfully started 153 * 154 * @return true if the nanoapp with the given instance ID was found & unloaded 155 */ 156 bool unloadNanoapp(uint16_t instanceId, bool allowSystemNanoappUnload, 157 bool nanoappStarted = true); 158 159 /** 160 * Executes the loop that blocks on the event queue and delivers received 161 * events to nanoapps. Only returns after stop() is called (from another 162 * context). 163 */ 164 void run(); 165 166 /** 167 * Signals the event loop currently executing in run() to exit gracefully at 168 * the next available opportunity. This function is thread-safe. 169 */ 170 void stop(); 171 172 /** 173 * Synchronously distributes an event to all nanoapps that should receive it. 174 * The event is sent from the system to a specific nanoapp if targetInstanceId 175 * matches the nanoappId, or to all registered nanoapps if targetInstanceId 176 * is set to kBroadcastInstanceId 177 * 178 * This is intended to be used by the function provided to 179 * EventLoopManager::deferCallback in cases where pre- and post-processing are 180 * required around event delivery. This closes the gaps around event delivery 181 * and can remove the need for posting multiple events. 182 * 183 * This must only be used from the EventLoop thread, and must only be used in 184 * rare circumstances where one of the postEvent functions cannot be used. In 185 * particular, misuse of this API can break explicit and implicit event 186 * ordering guarantees and trigger subtle bugs in nanoapps, so use with 187 * caution. 188 * 189 * No freeCallback is provided. The caller is expected to manage the memory 190 * for eventData, and handle any cleanup. 191 * 192 * @param eventType Event type identifier, which implies the type of eventData 193 * @param eventData The data being posted 194 * @param targetInstanceId The instance ID of the destination of this event 195 * @param targetGroupMask Mask used to limit the recipients that are 196 * registered to receive this event 197 */ 198 bool distributeEventSync(uint16_t eventType, void *eventData, 199 uint16_t targetInstanceId = kBroadcastInstanceId, 200 uint16_t targetGroupMask = kDefaultTargetGroupMask); 201 202 /** 203 * Posts an event to a nanoapp that is currently running (or all nanoapps if 204 * the target instance ID is kBroadcastInstanceId). A senderInstanceId cannot 205 * be provided to this method because it must only be used to post events 206 * sent by the system. If the event fails to post and the event loop thread is 207 * running, this is considered a fatal error. If the thread is not running 208 * (e.g. CHRE is shutting down), the event is silently dropped and the free 209 * callback is invoked prior to returning (if not null). 210 * 211 * Safe to call from any thread. 212 * 213 * @param eventType Event type identifier, which implies the type of eventData 214 * @param eventData The data being posted 215 * @param freeCallback Function to invoke to when the event has been processed 216 * by all recipients; this must be safe to call immediately, to handle 217 * the case where CHRE is shutting down 218 * @param targetInstanceId The instance ID of the destination of this event 219 * @param targetGroupMask Mask used to limit the recipients that are 220 * registered to receive this event 221 * 222 * @see postLowPriorityEventOrFree 223 */ 224 void postEventOrDie(uint16_t eventType, void *eventData, 225 chreEventCompleteFunction *freeCallback, 226 uint16_t targetInstanceId = kBroadcastInstanceId, 227 uint16_t targetGroupMask = kDefaultTargetGroupMask); 228 229 /** 230 * Posts an event to a nanoapp that is currently running (or all nanoapps if 231 * the target instance ID is kBroadcastInstanceId). If the event fails to 232 * post, freeCallback is invoked prior to returning (if not null). 233 * 234 * Safe to call from any thread. 235 * 236 * @param eventType Event type identifier, which implies the type of eventData 237 * @param eventData The data being posted 238 * @param freeCallback Function to invoke to when the event has been processed 239 * by all recipients; this must be safe to call immediately, to handle 240 * the case where CHRE is shutting down 241 * @param senderInstanceId The instance ID of the sender of this event 242 * @param targetInstanceId The instance ID of the destination of this event 243 * @param targetGroupMask Mask used to limit the recipients that are 244 * registered to receive this event 245 * 246 * @return true if the event was successfully added to the queue. 247 * 248 * @see chreSendEvent 249 */ 250 bool postLowPriorityEventOrFree( 251 uint16_t eventType, void *eventData, 252 chreEventCompleteFunction *freeCallback, 253 uint16_t senderInstanceId = kSystemInstanceId, 254 uint16_t targetInstanceId = kBroadcastInstanceId, 255 uint16_t targetGroupMask = kDefaultTargetGroupMask); 256 257 /** 258 * Posts an event for processing by the system from within the context of the 259 * CHRE thread. Uses the same underlying event queue as is used for nanoapp 260 * events, but gives the ability to provide an additional data pointer. If the 261 * event loop is running and the system event can't be posted (i.e. queue is 262 * full), then a fatal error is raised. 263 * 264 * Safe to call from any thread. 265 * 266 * @param eventType Event type identifier, which is forwarded to the callback 267 * @param eventData Arbitrary data to pass to the callback 268 * @param callback Function to invoke from the context of the CHRE thread 269 * @param extraData Additional arbitrary data to provide to the callback 270 * 271 * @return true if successfully posted; false ONLY IF the CHRE event loop is 272 * shutting down and not accepting any new events - in this case, 273 * the callback will not be invoked and any allocated memory must be 274 * cleaned up 275 * 276 * @see postEventOrDie 277 * @see EventLoopManager::deferCallback 278 */ 279 bool postSystemEvent(uint16_t eventType, void *eventData, 280 SystemEventCallbackFunction *callback, void *extraData); 281 282 /** 283 * Returns a pointer to the currently executing Nanoapp, or nullptr if none is 284 * currently executing. Must only be called from within the thread context 285 * associated with this EventLoop. 286 * 287 * @return the currently executing nanoapp, or nullptr 288 */ getCurrentNanoapp()289 Nanoapp *getCurrentNanoapp() const { 290 return mCurrentApp; 291 } 292 293 /** 294 * Gets the number of nanoapps currently associated with this event loop. Must 295 * only be called within the context of this EventLoop. 296 * 297 * @return The number of nanoapps managed by this event loop 298 */ getNanoappCount()299 size_t getNanoappCount() const { 300 return mNanoapps.size(); 301 } 302 303 /** 304 * Obtains the TimerPool associated with this event loop. 305 * 306 * @return The timer pool owned by this event loop. 307 */ getTimerPool()308 TimerPool &getTimerPool() { 309 return mTimerPool; 310 } 311 312 /** 313 * Searches the set of nanoapps managed by this EventLoop for one with the 314 * given instance ID. 315 * 316 * This function is safe to call from any thread. 317 * 318 * @param instanceId The nanoapp instance ID to search for. 319 * @return a pointer to the found nanoapp or nullptr if no match was found. 320 */ 321 Nanoapp *findNanoappByInstanceId(uint16_t instanceId) const; 322 323 /** 324 * Searches the set of nanoapps managed by this EventLoop for one with the 325 * given nanoapp ID. 326 * 327 * This function is safe to call from any thread. 328 * 329 * @param appId The nanoapp ID to search for. 330 * @return a pointer to the found nanoapp or nullptr if no match was found. 331 */ 332 Nanoapp *findNanoappByAppId(uint64_t appId) const; 333 334 /** 335 * Looks for an app with the given ID and if found, populates info with its 336 * metadata. Safe to call from any thread. 337 * 338 * @see chreGetNanoappInfoByAppId 339 */ 340 bool populateNanoappInfoForAppId(uint64_t appId, 341 struct chreNanoappInfo *info) const; 342 343 /** 344 * Looks for an app with the given instance ID and if found, populates info 345 * with its metadata. Safe to call from any thread. 346 * 347 * @see chreGetNanoappInfoByInstanceId 348 */ 349 bool populateNanoappInfoForInstanceId(uint16_t instanceId, 350 struct chreNanoappInfo *info) const; 351 352 /** 353 * @return true if the current Nanoapp (or entire CHRE) is being unloaded, and 354 * therefore it should not be allowed to send events or messages, etc. 355 */ 356 bool currentNanoappIsStopping() const; 357 358 /** 359 * Prints state in a string buffer. Must only be called from the context of 360 * the main CHRE thread. 361 * 362 * @param debugDump The debug dump wrapper where a string can be printed 363 * into one of the buffers. 364 */ 365 void logStateToBuffer(DebugDumpWrapper &debugDump) const; 366 367 /** 368 * Executes function for each nanoapp in the event loop. If function 369 * returns true, the iteration will stop. 370 * 371 * This function is safe to call from any thread. 372 * 373 * @param function The function to execute for each nanoapp. 374 */ 375 void onMatchingNanoappEndpoint( 376 const pw::Function<bool(const message::EndpointInfo &)> &function); 377 378 /** 379 * Executes function for each service provided by a nanoapp in the event 380 * loop. If function returns true, the iteration will stop. 381 * 382 * This function is safe to call from any thread. 383 * 384 * @param function The function to execute for each service. 385 */ 386 void onMatchingNanoappService( 387 const pw::Function<bool(const message::EndpointInfo &, 388 const message::ServiceInfo &)> &function); 389 390 /** 391 * Returns the EndpointInfo for the given nanoapp. 392 * 393 * This function is safe to call from any thread. 394 * 395 * @param appId The nanoapp ID to search for. 396 * @return The EndpointInfo for the given nanoapp, or std::nullopt if not 397 * found. 398 */ 399 std::optional<message::EndpointInfo> getEndpointInfo(uint64_t appId); 400 401 /** 402 * Returns a reference to the power control manager. This allows power 403 * controls from subsystems outside the event loops. 404 */ getPowerControlManager()405 PowerControlManager &getPowerControlManager() { 406 return mPowerControlManager; 407 } 408 getMaxEventQueueSize()409 inline uint32_t getMaxEventQueueSize() const { 410 return mEventPoolUsage.getMax(); 411 } 412 getNumEventsDropped()413 inline uint32_t getNumEventsDropped() const { 414 return mNumDroppedLowPriEvents; 415 } 416 417 private: 418 #ifdef CHRE_STATIC_EVENT_LOOP 419 //! The maximum number of events that can be active in the system. 420 static constexpr size_t kMaxEventCount = CHRE_MAX_EVENT_COUNT; 421 422 //! The maximum number of events that are awaiting to be scheduled. These 423 //! events are in a queue to be distributed to apps. 424 static constexpr size_t kMaxUnscheduledEventCount = 425 CHRE_MAX_UNSCHEDULED_EVENT_COUNT; 426 427 //! The memory pool to allocate incoming events from. 428 SynchronizedMemoryPool<Event, kMaxEventCount> mEventPool; 429 430 //! The blocking queue of incoming events from the system that have not been 431 //! distributed out to apps yet. 432 FixedSizeBlockingQueue<Event *, kMaxUnscheduledEventCount> mEvents; 433 434 #else 435 //! The maximum number of event that can be stored in a block in mEventPool. 436 static constexpr size_t kEventPerBlock = CHRE_EVENT_PER_BLOCK; 437 438 //! The maximum number of event blocks that mEventPool can hold. 439 static constexpr size_t kMaxEventBlock = CHRE_MAX_EVENT_BLOCKS; 440 441 static constexpr size_t kMaxEventCount = 442 CHRE_EVENT_PER_BLOCK * CHRE_MAX_EVENT_BLOCKS; 443 444 //! The memory pool to allocate incoming events from. 445 SynchronizedExpandableMemoryPool<Event, kEventPerBlock, kMaxEventBlock> 446 mEventPool; 447 448 //! The blocking queue of incoming events from the system that have not been 449 //! distributed out to apps yet. 450 BlockingSegmentedQueue<Event *, kEventPerBlock> mEvents; 451 #endif 452 453 //! The last time wakeup buckets were pushed onto the nanoapps. 454 Nanoseconds mTimeLastWakeupBucketCycled; 455 456 //! The timer used schedule timed events for tasks running in this event loop. 457 TimerPool mTimerPool; 458 459 //! The list of nanoapps managed by this event loop. 460 DynamicVector<UniquePtr<Nanoapp>> mNanoapps; 461 462 //! This lock *must* be held whenever we: 463 //! (1) make changes to the mNanoapps vector, or 464 //! (2) read the mNanoapps vector from a thread other than the one 465 //! associated with this EventLoop 466 //! It is not necessary to acquire the lock when reading mNanoapps from within 467 //! the thread context of this EventLoop. 468 mutable Mutex mNanoappsLock; 469 470 //! Indicates whether the event loop is running. 471 AtomicBool mRunning; 472 473 //! The nanoapp that is currently executing - must be set any time we call 474 //! into the nanoapp's entry points or callbacks 475 Nanoapp *mCurrentApp = nullptr; 476 477 //! Set to the nanoapp we are in the process of unloading in unloadNanoapp() 478 Nanoapp *mStoppingNanoapp = nullptr; 479 480 //! The object which manages power related controls. 481 PowerControlManager mPowerControlManager; 482 483 //! The stats collection used to collect event pool usage 484 StatsContainer<uint32_t> mEventPoolUsage; 485 486 //! The number of events dropped due to capacity limits 487 uint32_t mNumDroppedLowPriEvents = 0; 488 489 //! The timer used to cycle nanoapp wakeup buckets. 490 TimerHandle mCycleWakeupBucketsHandle = CHRE_TIMER_INVALID; 491 492 /** 493 * Modifies the run loop state so it no longer iterates on new events. This 494 * should only be invoked by the event loop when it is ready to stop 495 * processing new events. 496 */ 497 void onStopComplete(); 498 499 /** 500 * Allocates an event from the event pool and post it. 501 * 502 * @return true if the event has been successfully allocated and posted. 503 * 504 * @see postEventOrDie and postLowPriorityEventOrFree 505 */ 506 bool allocateAndPostEvent(uint16_t eventType, void *eventData, 507 chreEventCompleteFunction *freeCallback, 508 bool isLowPriority, uint16_t senderInstanceId, 509 uint16_t targetInstanceId, 510 uint16_t targetGroupMask); 511 /** 512 * Remove some non nanoapp and low priority events from back of the queue. 513 * 514 * @param removeNum Number of low priority events to be removed. 515 * @return False if cannot remove any low priority event. 516 */ 517 bool removeNonNanoappLowPriorityEventsFromBack(size_t removeNum); 518 519 /** 520 * Determine if there are space for high priority event. 521 * During the processing of determining the vacant space, it might 522 * remove low priority events to make space for high priority event. 523 * 524 * @return true if there are no space for a new high priority event. 525 */ 526 bool hasNoSpaceForHighPriorityEvent(); 527 528 /** 529 * Delivers the next event pending to the Nanoapp. 530 */ 531 void deliverNextEvent(const UniquePtr<Nanoapp> &app, Event *event); 532 533 /** 534 * Given an event pulled from the main incoming event queue (mEvents), deliver 535 * it to all Nanoapps that should receive the event, or free the event if 536 * there are no valid recipients. 537 * 538 * @param event The Event to distribute to Nanoapps 539 */ 540 void distributeEvent(Event *event); 541 542 /** 543 * Shared functionality to distributeEvent and distributeEventSync. Should 544 * only be called by those functions. Hnadles event distribution and logging 545 * without any pre- or post-processing. 546 * 547 * @param event The Event to distribute to Nanoapps 548 * @return True if the event was delivered to any nanoapps, otherwise false 549 */ 550 bool distributeEventCommon(Event *event); 551 552 /** 553 * Distribute all events pending in the inbound event queue. Note that this 554 * function only guarantees that any events in the inbound queue at the time 555 * it is called will be distributed to Nanoapp event queues - new events may 556 * still be posted during or after this function call from other threads as 557 * long as postEvent() will accept them. 558 */ 559 void flushInboundEventQueue(); 560 561 /** 562 * Call after when an Event has been delivered to all intended recipients. 563 * Invokes the event's free callback (if given) and releases resources. 564 * 565 * @param event The event to be freed 566 */ 567 void freeEvent(Event *event); 568 569 /** 570 * Finds a Nanoapp with the given 64-bit appId. 571 * 572 * Only safe to call within this EventLoop's thread, or if mNanoappsLock is 573 * held. 574 * 575 * @param appId Nanoapp ID 576 * @return Pointer to Nanoapp instance in this EventLoop with the given app 577 * ID, or nullptr if not found 578 */ 579 Nanoapp *lookupAppByAppId(uint64_t appId) const; 580 581 /** 582 * Finds a Nanoapp with the given instanceId. 583 * 584 * Only safe to call within this EventLoop's thread, or if mNanoappsLock is 585 * held. 586 * 587 * @param instanceId Nanoapp instance identifier 588 * @return Nanoapp with the given instanceId, or nullptr if not found 589 */ 590 Nanoapp *lookupAppByInstanceId(uint16_t instanceId) const; 591 592 /** 593 * Sends an event with payload struct chreNanoappInfo populated from the given 594 * Nanoapp instance to inform other nanoapps about it starting/stopping. 595 * 596 * @param eventType Should be one of CHRE_EVENT_NANOAPP_{STARTED, STOPPED} 597 * @param nanoapp The nanoapp instance whose status has changed 598 */ 599 void notifyAppStatusChange(uint16_t eventType, const Nanoapp &nanoapp); 600 601 /** 602 * Stops and unloads the Nanoapp at the given index in mNanoapps. 603 * 604 * IMPORTANT: prior to calling this function, the event queues must be in a 605 * safe condition for removal of this nanoapp. This means that there must not 606 * be any pending events in this nanoapp's queue, and there must not be any 607 * outstanding events sent by this nanoapp, as they may reference the 608 * nanoapp's own memory (even if there is no free callback). 609 * 610 * @param index Index of the nanoapp in the list of nanoapps managed by event 611 * loop. 612 * @param nanoappStarted Indicates whether the nanoapp successfully started 613 */ 614 void unloadNanoappAtIndex(size_t index, bool nanoappStarted = true); 615 616 /** 617 * Logs dangling resources when a nanoapp is unloaded. 618 * 619 * @param name The name of the resource. 620 * @param count The number of dangling resources. 621 */ 622 void logDanglingResources(const char *name, uint32_t count); 623 624 /** 625 * Returns the EndpointInfo for the given nanoapp. 626 * 627 * Only safe to call within this EventLoop's thread, or if mNanoappsLock is 628 * held. 629 * 630 * @param nanoapp The nanoapp to get the EndpointInfo for. 631 * @return The EndpointInfo for the given nanoapp 632 */ 633 message::EndpointInfo getEndpointInfoFromNanoappLocked( 634 const Nanoapp &nanoapp); 635 636 /* 637 * Pushes new wakeup buckets that need to be pushed to nanoapps. 638 */ 639 void handleNanoappWakeupBuckets(); 640 641 /* 642 * Set up the timer used for calling handleNanoappWakeupBuckets() to cycle 643 * wakeup buckets once the wakeup bucket interval has been surpassed. 644 */ 645 void setCycleWakeupBucketsTimer(); 646 }; 647 648 } // namespace chre 649 650 #endif // CHRE_CORE_EVENT_LOOP_H_ 651