Adds writer test case for RCU
[folly.git] / folly / experimental / logging / LoggerDB.cpp
1 /*
2  * Copyright 2017-present Facebook, Inc.
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 #include <folly/experimental/logging/LoggerDB.h>
17
18 #include <set>
19
20 #include <folly/Conv.h>
21 #include <folly/FileUtil.h>
22 #include <folly/String.h>
23 #include <folly/experimental/logging/LogCategory.h>
24 #include <folly/experimental/logging/LogConfig.h>
25 #include <folly/experimental/logging/LogHandler.h>
26 #include <folly/experimental/logging/LogHandlerFactory.h>
27 #include <folly/experimental/logging/LogLevel.h>
28 #include <folly/experimental/logging/Logger.h>
29 #include <folly/experimental/logging/RateLimiter.h>
30
31 using std::string;
32
33 namespace folly {
34
35 namespace {
36 class LoggerDBSingleton {
37  public:
38   explicit LoggerDBSingleton(LoggerDB* db) : db_{db} {}
39   ~LoggerDBSingleton() {
40     // We intentionally leak the LoggerDB object on destruction.
41     // We want Logger objects to remain valid for the entire lifetime of the
42     // program, without having to worry about destruction ordering issues, or
43     // making the Logger perform reference counting on the LoggerDB.
44     //
45     // Therefore the main LoggerDB object, and all of the LogCategory objects
46     // it contains, are always intentionally leaked.
47     //
48     // However, we do call db_->cleanupHandlers() to destroy any registered
49     // LogHandler objects.  The LogHandlers can be user-defined objects and may
50     // hold resources that should be cleaned up.  This also ensures that the
51     // LogHandlers flush all outstanding messages before we exit.
52     db_->cleanupHandlers();
53   }
54
55   LoggerDB* getDB() const {
56     return db_;
57   }
58
59  private:
60   LoggerDB* db_;
61 };
62 } // namespace
63
64 LoggerDB* LoggerDB::get() {
65   // Intentionally leaky singleton
66   static LoggerDBSingleton singleton{new LoggerDB()};
67   return singleton.getDB();
68 }
69
70 LoggerDB::LoggerDB() {
71   // Create the root log category, and set the level to ERR by default
72   auto rootUptr = std::make_unique<LogCategory>(this);
73   LogCategory* root = rootUptr.get();
74   auto ret =
75       loggersByName_.wlock()->emplace(root->getName(), std::move(rootUptr));
76   DCHECK(ret.second);
77
78   root->setLevelLocked(LogLevel::ERR, false);
79 }
80
81 LoggerDB::LoggerDB(TestConstructorArg) : LoggerDB() {}
82
83 LoggerDB::~LoggerDB() {}
84
85 LogCategory* LoggerDB::getCategory(StringPiece name) {
86   return getOrCreateCategoryLocked(*loggersByName_.wlock(), name);
87 }
88
89 LogCategory* FOLLY_NULLABLE LoggerDB::getCategoryOrNull(StringPiece name) {
90   auto loggersByName = loggersByName_.rlock();
91
92   auto it = loggersByName->find(name);
93   if (it == loggersByName->end()) {
94     return nullptr;
95   }
96   return it->second.get();
97 }
98
99 void LoggerDB::setLevel(folly::StringPiece name, LogLevel level, bool inherit) {
100   auto loggersByName = loggersByName_.wlock();
101   LogCategory* category = getOrCreateCategoryLocked(*loggersByName, name);
102   category->setLevelLocked(level, inherit);
103 }
104
105 void LoggerDB::setLevel(LogCategory* category, LogLevel level, bool inherit) {
106   auto loggersByName = loggersByName_.wlock();
107   category->setLevelLocked(level, inherit);
108 }
109
110 LogConfig LoggerDB::getConfig() const {
111   auto handlerInfo = handlerInfo_.rlock();
112
113   LogConfig::HandlerConfigMap handlerConfigs;
114   std::unordered_map<std::shared_ptr<LogHandler>, string> handlersToName;
115   for (const auto& entry : handlerInfo->handlers) {
116     auto handler = entry.second.lock();
117     if (!handler) {
118       continue;
119     }
120     handlersToName.emplace(handler, entry.first);
121     handlerConfigs.emplace(entry.first, handler->getConfig());
122   }
123
124   size_t anonymousNameIndex = 1;
125   auto generateAnonymousHandlerName = [&]() {
126     // Return a unique name of the form "anonymousHandlerN"
127     // Keep incrementing N until we find a name that isn't currently taken.
128     while (true) {
129       auto name = to<string>("anonymousHandler", anonymousNameIndex);
130       ++anonymousNameIndex;
131       if (handlerInfo->handlers.find(name) == handlerInfo->handlers.end()) {
132         return name;
133       }
134     }
135   };
136
137   LogConfig::CategoryConfigMap categoryConfigs;
138   {
139     auto loggersByName = loggersByName_.rlock();
140     for (const auto& entry : *loggersByName) {
141       auto* category = entry.second.get();
142       auto levelInfo = category->getLevelInfo();
143       auto handlers = category->getHandlers();
144
145       // Don't report categories that have default settings.
146       if (handlers.empty() && levelInfo.first == LogLevel::MAX_LEVEL &&
147           levelInfo.second) {
148         continue;
149       }
150
151       // Translate the handler pointers to names
152       std::vector<string> handlerNames;
153       for (const auto& handler : handlers) {
154         auto iter = handlersToName.find(handler);
155         if (iter == handlersToName.end()) {
156           // This LogHandler must have been manually attached to the category,
157           // rather than defined with `updateConfig()` or `resetConfig()`.
158           // Generate a unique name to use for reporting it in the config.
159           auto name = generateAnonymousHandlerName();
160           handlersToName.emplace(handler, name);
161           handlerConfigs.emplace(name, handler->getConfig());
162           handlerNames.emplace_back(name);
163         } else {
164           handlerNames.emplace_back(iter->second);
165         }
166       }
167
168       LogCategoryConfig categoryConfig(
169           levelInfo.first, levelInfo.second, handlerNames);
170       categoryConfigs.emplace(category->getName(), std::move(categoryConfig));
171     }
172   }
173
174   return LogConfig{std::move(handlerConfigs), std::move(categoryConfigs)};
175 }
176
177 /**
178  * Process handler config information when starting a config update operation.
179  */
180 void LoggerDB::startConfigUpdate(
181     const Synchronized<HandlerInfo>::LockedPtr& handlerInfo,
182     const LogConfig& config,
183     NewHandlerMap* handlers,
184     OldToNewHandlerMap* oldToNewHandlerMap) {
185   // Get a map of all currently existing LogHandlers.
186   // This resolves weak_ptrs to shared_ptrs, and ignores expired weak_ptrs.
187   // This prevents any of these LogHandler pointers from expiring during the
188   // config update.
189   for (const auto& entry : handlerInfo->handlers) {
190     auto handler = entry.second.lock();
191     if (handler) {
192       handlers->emplace(entry.first, std::move(handler));
193     }
194   }
195
196   // Create all of the new LogHandlers needed from this configuration
197   for (const auto& entry : config.getHandlerConfigs()) {
198     // Check to see if there is an existing LogHandler with this name
199     std::shared_ptr<LogHandler> oldHandler;
200     auto iter = handlers->find(entry.first);
201     if (iter != handlers->end()) {
202       oldHandler = iter->second;
203     }
204
205     LogHandlerConfig updatedConfig;
206     const LogHandlerConfig* handlerConfig;
207     if (entry.second.type.hasValue()) {
208       handlerConfig = &entry.second;
209     } else {
210       // This configuration is intended to update an existing LogHandler
211       if (!oldHandler) {
212         throw std::invalid_argument(to<std::string>(
213             "cannot update unknown log handler \"", entry.first, "\""));
214       }
215
216       updatedConfig = oldHandler->getConfig();
217       if (!updatedConfig.type.hasValue()) {
218         // This normally should not happen unless someone improperly manually
219         // constructed a LogHandler object.  All existing LogHandler objects
220         // should indicate their type.
221         throw std::invalid_argument(to<std::string>(
222             "existing log handler \"",
223             entry.first,
224             "\" is missing type information"));
225       }
226       updatedConfig.update(entry.second);
227       handlerConfig = &updatedConfig;
228     }
229
230     // Look up the LogHandlerFactory
231     auto factoryIter = handlerInfo->factories.find(handlerConfig->type.value());
232     if (factoryIter == handlerInfo->factories.end()) {
233       throw std::invalid_argument(to<std::string>(
234           "unknown log handler type \"", handlerConfig->type.value(), "\""));
235     }
236
237     // Create the new log handler
238     const auto& factory = factoryIter->second;
239     std::shared_ptr<LogHandler> handler;
240     try {
241       if (oldHandler) {
242         handler = factory->updateHandler(oldHandler, handlerConfig->options);
243         if (handler != oldHandler) {
244           oldToNewHandlerMap->emplace(oldHandler, handler);
245         }
246       } else {
247         handler = factory->createHandler(handlerConfig->options);
248       }
249     } catch (const std::exception& ex) {
250       // Errors creating or updating the the log handler are generally due to
251       // bad configuration options.  It is useful to update the exception
252       // message to include the name of the log handler we were trying to
253       // update or create.
254       throw std::invalid_argument(to<string>(
255           "error ",
256           oldHandler ? "updating" : "creating",
257           " log handler \"",
258           entry.first,
259           "\": ",
260           exceptionStr(ex)));
261     }
262     handlerInfo->handlers[entry.first] = handler;
263     (*handlers)[entry.first] = handler;
264   }
265
266   // Before we start making any LogCategory changes, confirm that all handlers
267   // named in the category configs are known handlers.
268   for (const auto& entry : config.getCategoryConfigs()) {
269     if (!entry.second.handlers.hasValue()) {
270       continue;
271     }
272     for (const auto& handlerName : entry.second.handlers.value()) {
273       auto iter = handlers->find(handlerName);
274       if (iter == handlers->end()) {
275         throw std::invalid_argument(to<std::string>(
276             "unknown log handler \"",
277             handlerName,
278             "\" configured for log category \"",
279             entry.first,
280             "\""));
281       }
282     }
283   }
284 }
285
286 /**
287  * Update handlerInfo_ at the end of a config update operation.
288  */
289 void LoggerDB::finishConfigUpdate(
290     const Synchronized<HandlerInfo>::LockedPtr& handlerInfo,
291     NewHandlerMap* handlers,
292     OldToNewHandlerMap* oldToNewHandlerMap) {
293   // Build a new map to replace handlerInfo->handlers
294   // This will contain only the LogHandlers that are still in use by the
295   // current LogCategory settings.
296   std::unordered_map<std::string, std::weak_ptr<LogHandler>> newHandlerMap;
297   for (const auto& entry : *handlers) {
298     newHandlerMap.emplace(entry.first, entry.second);
299   }
300   // Drop all of our shared_ptr references to LogHandler objects,
301   // and then remove entries in newHandlerMap that are unreferenced.
302   handlers->clear();
303   oldToNewHandlerMap->clear();
304   handlerInfo->handlers.clear();
305   for (auto iter = newHandlerMap.begin(); iter != newHandlerMap.end(); /**/) {
306     if (iter->second.expired()) {
307       iter = newHandlerMap.erase(iter);
308     } else {
309       ++iter;
310     }
311   }
312   handlerInfo->handlers.swap(newHandlerMap);
313 }
314
315 std::vector<std::shared_ptr<LogHandler>> LoggerDB::buildCategoryHandlerList(
316     const NewHandlerMap& handlerMap,
317     StringPiece categoryName,
318     const std::vector<std::string>& categoryHandlerNames) {
319   std::vector<std::shared_ptr<LogHandler>> catHandlers;
320   for (const auto& handlerName : categoryHandlerNames) {
321     auto iter = handlerMap.find(handlerName);
322     if (iter == handlerMap.end()) {
323       // This really shouldn't be possible; the checks in startConfigUpdate()
324       // should have already bailed out if there was an unknown handler.
325       throw std::invalid_argument(to<std::string>(
326           "bug: unknown log handler \"",
327           handlerName,
328           "\" configured for log category \"",
329           categoryName,
330           "\""));
331     }
332     catHandlers.push_back(iter->second);
333   }
334
335   return catHandlers;
336 }
337
338 void LoggerDB::updateConfig(const LogConfig& config) {
339   // Grab the handlerInfo_ lock.
340   // We hold it in write mode for the entire config update operation.  This
341   // ensures that only a single config update operation ever runs at once.
342   auto handlerInfo = handlerInfo_.wlock();
343
344   NewHandlerMap handlers;
345   OldToNewHandlerMap oldToNewHandlerMap;
346   startConfigUpdate(handlerInfo, config, &handlers, &oldToNewHandlerMap);
347
348   // If an existing LogHandler was replaced with a new one,
349   // walk all current LogCategories and replace this handler.
350   if (!oldToNewHandlerMap.empty()) {
351     auto loggerMap = loggersByName_.rlock();
352     for (const auto& entry : *loggerMap) {
353       entry.second->updateHandlers(oldToNewHandlerMap);
354     }
355   }
356
357   // Update log levels and handlers mentioned in the config update
358   auto loggersByName = loggersByName_.wlock();
359   for (const auto& entry : config.getCategoryConfigs()) {
360     LogCategory* category =
361         getOrCreateCategoryLocked(*loggersByName, entry.first);
362
363     // Update the log handlers
364     if (entry.second.handlers.hasValue()) {
365       auto catHandlers = buildCategoryHandlerList(
366           handlers, entry.first, entry.second.handlers.value());
367       category->replaceHandlers(std::move(catHandlers));
368     }
369
370     // Update the level settings
371     category->setLevelLocked(
372         entry.second.level, entry.second.inheritParentLevel);
373   }
374
375   finishConfigUpdate(handlerInfo, &handlers, &oldToNewHandlerMap);
376 }
377
378 void LoggerDB::resetConfig(const LogConfig& config) {
379   // Grab the handlerInfo_ lock.
380   // We hold it in write mode for the entire config update operation.  This
381   // ensures that only a single config update operation ever runs at once.
382   auto handlerInfo = handlerInfo_.wlock();
383
384   NewHandlerMap handlers;
385   OldToNewHandlerMap oldToNewHandlerMap;
386   startConfigUpdate(handlerInfo, config, &handlers, &oldToNewHandlerMap);
387
388   // Make sure all log categories mentioned in the new config exist.
389   // This ensures that we will cover them in our walk below.
390   LogCategory* rootCategory;
391   {
392     auto loggersByName = loggersByName_.wlock();
393     rootCategory = getOrCreateCategoryLocked(*loggersByName, "");
394     for (const auto& entry : config.getCategoryConfigs()) {
395       getOrCreateCategoryLocked(*loggersByName, entry.first);
396     }
397   }
398
399   {
400     // Update all log categories
401     auto loggersByName = loggersByName_.rlock();
402     for (const auto& entry : *loggersByName) {
403       auto* category = entry.second.get();
404
405       auto configIter = config.getCategoryConfigs().find(category->getName());
406       if (configIter == config.getCategoryConfigs().end()) {
407         // This category is not listed in the config settings.
408         // Reset it to the default settings.
409         category->clearHandlers();
410
411         if (category == rootCategory) {
412           category->setLevelLocked(LogLevel::ERR, false);
413         } else {
414           category->setLevelLocked(LogLevel::MAX_LEVEL, true);
415         }
416         continue;
417       }
418
419       const auto& catConfig = configIter->second;
420
421       // Update the category log level
422       category->setLevelLocked(catConfig.level, catConfig.inheritParentLevel);
423
424       // Update the category handlers list.
425       // If the handler list is not set in the config, clear out any existing
426       // handlers rather than leaving it as-is.
427       std::vector<std::shared_ptr<LogHandler>> catHandlers;
428       if (catConfig.handlers.hasValue()) {
429         catHandlers = buildCategoryHandlerList(
430             handlers, entry.first, catConfig.handlers.value());
431       }
432       category->replaceHandlers(std::move(catHandlers));
433     }
434   }
435
436   finishConfigUpdate(handlerInfo, &handlers, &oldToNewHandlerMap);
437 }
438
439 LogCategory* LoggerDB::getOrCreateCategoryLocked(
440     LoggerNameMap& loggersByName,
441     StringPiece name) {
442   auto it = loggersByName.find(name);
443   if (it != loggersByName.end()) {
444     return it->second.get();
445   }
446
447   StringPiece parentName = LogName::getParent(name);
448   LogCategory* parent = getOrCreateCategoryLocked(loggersByName, parentName);
449   return createCategoryLocked(loggersByName, name, parent);
450 }
451
452 LogCategory* LoggerDB::createCategoryLocked(
453     LoggerNameMap& loggersByName,
454     StringPiece name,
455     LogCategory* parent) {
456   auto uptr = std::make_unique<LogCategory>(name, parent);
457   LogCategory* logger = uptr.get();
458   auto ret = loggersByName.emplace(logger->getName(), std::move(uptr));
459   DCHECK(ret.second);
460   return logger;
461 }
462
463 void LoggerDB::cleanupHandlers() {
464   // Get a copy of all categories, so we can call clearHandlers() without
465   // holding the loggersByName_ lock.  We don't need to worry about LogCategory
466   // lifetime, since LogCategory objects always live for the lifetime of the
467   // LoggerDB.
468   std::vector<LogCategory*> categories;
469   {
470     auto loggersByName = loggersByName_.wlock();
471     categories.reserve(loggersByName->size());
472     for (const auto& entry : *loggersByName) {
473       categories.push_back(entry.second.get());
474     }
475   }
476
477   // Also extract our HandlerFactoryMap and HandlerMap, so we can clear them
478   // later without holding the handlerInfo_ lock.
479   HandlerFactoryMap factories;
480   HandlerMap handlers;
481   {
482     auto handlerInfo = handlerInfo_.wlock();
483     factories.swap(handlerInfo->factories);
484     handlers.swap(handlerInfo->handlers);
485   }
486
487   // Remove all of the LogHandlers from all log categories,
488   // to drop any shared_ptr references to the LogHandlers
489   for (auto* category : categories) {
490     category->clearHandlers();
491   }
492 }
493
494 size_t LoggerDB::flushAllHandlers() {
495   // Build a set of all LogHandlers.  We use a set to avoid calling flush()
496   // more than once on the same handler if it is registered on multiple
497   // different categories.
498   std::set<std::shared_ptr<LogHandler>> handlers;
499   {
500     auto loggersByName = loggersByName_.wlock();
501     for (const auto& entry : *loggersByName) {
502       for (const auto& handler : entry.second->getHandlers()) {
503         handlers.emplace(handler);
504       }
505     }
506   }
507
508   // Call flush() on each handler
509   for (const auto& handler : handlers) {
510     handler->flush();
511   }
512   return handlers.size();
513 }
514
515 void LoggerDB::registerHandlerFactory(
516     std::unique_ptr<LogHandlerFactory> factory,
517     bool replaceExisting) {
518   auto type = factory->getType();
519   auto handlerInfo = handlerInfo_.wlock();
520   if (replaceExisting) {
521     handlerInfo->factories[type.str()] = std::move(factory);
522   } else {
523     auto ret = handlerInfo->factories.emplace(type.str(), std::move(factory));
524     if (!ret.second) {
525       throw std::range_error(to<std::string>(
526           "a LogHandlerFactory for the type \"", type, "\" already exists"));
527     }
528   }
529 }
530
531 void LoggerDB::unregisterHandlerFactory(StringPiece type) {
532   auto handlerInfo = handlerInfo_.wlock();
533   auto numRemoved = handlerInfo->factories.erase(type.str());
534   if (numRemoved != 1) {
535     throw std::range_error(
536         to<std::string>("no LogHandlerFactory for type \"", type, "\" found"));
537   }
538 }
539
540 LogLevel LoggerDB::xlogInit(
541     StringPiece categoryName,
542     std::atomic<LogLevel>* xlogCategoryLevel,
543     LogCategory** xlogCategory) {
544   // Hold the lock for the duration of the operation
545   // xlogInit() may be called from multiple threads simultaneously.
546   // Only one needs to perform the initialization.
547   auto loggersByName = loggersByName_.wlock();
548   if (xlogCategory != nullptr && *xlogCategory != nullptr) {
549     // The xlogCategory was already initialized before we acquired the lock
550     return (*xlogCategory)->getEffectiveLevel();
551   }
552
553   auto* category = getOrCreateCategoryLocked(*loggersByName, categoryName);
554   if (xlogCategory) {
555     // Set *xlogCategory before we update xlogCategoryLevel below.
556     // This is important, since the XLOG() macros check xlogCategoryLevel to
557     // tell if *xlogCategory has been initialized yet.
558     *xlogCategory = category;
559   }
560   auto level = category->getEffectiveLevel();
561   xlogCategoryLevel->store(level, std::memory_order_release);
562   category->registerXlogLevel(xlogCategoryLevel);
563   return level;
564 }
565
566 LogCategory* LoggerDB::xlogInitCategory(
567     StringPiece categoryName,
568     LogCategory** xlogCategory,
569     std::atomic<bool>* isInitialized) {
570   // Hold the lock for the duration of the operation
571   // xlogInitCategory() may be called from multiple threads simultaneously.
572   // Only one needs to perform the initialization.
573   auto loggersByName = loggersByName_.wlock();
574   if (isInitialized->load(std::memory_order_acquire)) {
575     // The xlogCategory was already initialized before we acquired the lock
576     return *xlogCategory;
577   }
578
579   auto* category = getOrCreateCategoryLocked(*loggersByName, categoryName);
580   *xlogCategory = category;
581   isInitialized->store(true, std::memory_order_release);
582   return category;
583 }
584
585 std::atomic<LoggerDB::InternalWarningHandler> LoggerDB::warningHandler_;
586
587 void LoggerDB::internalWarningImpl(
588     folly::StringPiece filename,
589     int lineNumber,
590     std::string&& msg) noexcept {
591   auto handler = warningHandler_.load();
592   if (handler) {
593     handler(filename, lineNumber, std::move(msg));
594   } else {
595     defaultInternalWarningImpl(filename, lineNumber, std::move(msg));
596   }
597 }
598
599 void LoggerDB::setInternalWarningHandler(InternalWarningHandler handler) {
600   // This API is intentionally pretty basic.  It has a number of limitations:
601   //
602   // - We only support plain function pointers, and not full std::function
603   //   objects.  This makes it possible to use std::atomic to access the
604   //   handler pointer, and also makes it safe to store in a zero-initialized
605   //   file-static pointer.
606   //
607   // - We don't support any void* argument to the handler.  The caller is
608   //   responsible for storing any callback state themselves.
609   //
610   // - When replacing or unsetting a handler we don't make any guarantees about
611   //   when the old handler will stop being called.  It may still be called
612   //   from other threads briefly even after setInternalWarningHandler()
613   //   returns.  This is also a consequence of using std::atomic rather than a
614   //   full lock.
615   //
616   // This provides the minimum capabilities needed to customize the handler,
617   // while still keeping the implementation simple and safe to use even before
618   // main().
619   warningHandler_.store(handler);
620 }
621
622 void LoggerDB::defaultInternalWarningImpl(
623     folly::StringPiece filename,
624     int lineNumber,
625     std::string&& msg) noexcept {
626   // Rate limit to 10 messages every 5 seconds.
627   //
628   // We intentonally use a leaky Meyer's singleton here over folly::Singleton:
629   // - We want this code to work even before main()
630   // - This singleton does not depend on any other singletons.
631   static auto* rateLimiter =
632       new logging::IntervalRateLimiter{10, std::chrono::seconds(5)};
633   if (!rateLimiter->check()) {
634     return;
635   }
636
637   if (folly::kIsDebug) {
638     // Write directly to file descriptor 2.
639     //
640     // It's possible the application has closed fd 2 and is using it for
641     // something other than stderr.  However we have no good way to detect
642     // this, which is the main reason we only write to stderr in debug build
643     // modes.  assert() also writes directly to stderr on failure, which seems
644     // like a reasonable precedent.
645     //
646     // Another option would be to use openlog() and syslog().  However
647     // calling openlog() may inadvertently affect the behavior of other parts
648     // of the program also using syslog().
649     //
650     // We don't check for write errors here, since there's not much else we can
651     // do if it fails.
652     auto fullMsg = folly::to<std::string>(
653         "logging warning:", filename, ":", lineNumber, ": ", msg, "\n");
654     folly::writeFull(STDERR_FILENO, fullMsg.data(), fullMsg.size());
655   }
656 }
657 } // namespace folly