folly-clib 20250713.1537 → 20260203.1245
raw patch · 790 files changed
+28846/−12219 lines, 790 files
This diff is very large; some files are shown as “too large to diff”. Download the raw patch for the complete diff.
Files
- folly-clib.cabal +130/−64
- folly/_build/folly/folly-config.h +3/−3
- folly/folly/AtomicHashArray.h +17/−17
- folly/folly/AtomicIntrusiveLinkedList.h +1/−1
- folly/folly/AtomicUnorderedMap.h +13/−14
- folly/folly/Benchmark.cpp +1036/−0
- folly/folly/Benchmark.h +62/−43
- folly/folly/BenchmarkUtil.cpp +149/−0
- folly/folly/BenchmarkUtil.h +15/−0
- folly/folly/CPortability.h +8/−0
- folly/folly/CancellationToken-inl.h +15/−11
- folly/folly/CancellationToken.h +21/−12
- folly/folly/ClockGettimeWrappers.cpp +1/−1
- folly/folly/ConcurrentBitSet.h +2/−2
- folly/folly/ConcurrentSkipList-inl.h +1/−1
- folly/folly/ConcurrentSkipList.h +33/−33
- folly/folly/ConstexprMath.h +3/−3
- folly/folly/Conv.h +19/−19
- folly/folly/CppAttributes.h +6/−0
- folly/folly/CpuId.h +13/−109
- folly/folly/Demangle.cpp +17/−9
- folly/folly/Demangle.h +2/−2
- folly/folly/DynamicConverter.h +1/−1
- folly/folly/ExceptionWrapper-inl.h +14/−7
- folly/folly/ExceptionWrapper.h +23/−7
- folly/folly/Executor.h +161/−126
- folly/folly/Expected.h +53/−54
- folly/folly/FBString.h +30/−2
- folly/folly/FBVector.h +1/−1
- folly/folly/File.cpp +6/−5
- folly/folly/FileUtil.cpp +11/−11
- folly/folly/FileUtil.h +8/−7
- folly/folly/Fingerprint.cpp +0/−1
- folly/folly/FollyMemcpy.cpp +1/−1
- folly/folly/FollyMemset.cpp +1/−1
- folly/folly/Format.cpp +1/−2
- folly/folly/Format.h +2/−7
- folly/folly/FormatTraits.h +4/−4
- folly/folly/Function.h +7/−6
- folly/folly/GroupVarint.h +8/−8
- folly/folly/Hash.h +1/−1
- folly/folly/IPAddress.cpp +20/−16
- folly/folly/IPAddressV4.cpp +10/−8
- folly/folly/IPAddressV4.h +2/−2
- folly/folly/IPAddressV6.cpp +18/−14
- folly/folly/IPAddressV6.h +3/−3
- folly/folly/Indestructible.h +0/−2
- folly/folly/IndexedMemPool.h +7/−6
- folly/folly/IntrusiveList.h +1/−1
- folly/folly/Lazy.h +1/−1
- folly/folly/MPMCPipeline.h +6/−4
- folly/folly/MPMCQueue.h +22/−96
- folly/folly/MapUtil.h +1/−1
- folly/folly/MaybeManagedPtr.h +7/−1
- folly/folly/Memory.h +45/−1
- folly/folly/MicroLock.h +1/−3
- folly/folly/ObserverContainer.h +36/−27
- folly/folly/OperationCancelled.h +22/−12
- folly/folly/Optional.h +14/−5
- folly/folly/PackedSyncPtr.h +1/−1
- folly/folly/Padded.h +10/−20
- folly/folly/Poly.h +2/−3
- folly/folly/Portability.h +25/−31
- folly/folly/ProducerConsumerQueue.h +1/−1
- folly/folly/Random-inl.h +11/−48
- folly/folly/Range.h +18/−4
- folly/folly/Replaceable.h +9/−6
- folly/folly/ScopeGuard.h +2/−2
- folly/folly/SharedMutex.h +15/−21
- folly/folly/Singleton-inl.h +44/−30
- folly/folly/Singleton.cpp +4/−1
- folly/folly/SingletonThreadLocal.h +21/−5
- folly/folly/SocketAddress.cpp +5/−4
- folly/folly/SocketAddress.h +28/−1
- folly/folly/String-inl.h +37/−0
- folly/folly/String.cpp +73/−7
- folly/folly/String.h +60/−0
- folly/folly/Subprocess.cpp +39/−11
- folly/folly/Subprocess.h +7/−3
- folly/folly/Synchronized.h +4/−5
- folly/folly/ThreadLocal.h +2/−2
- folly/folly/Traits.h +74/−26
- folly/folly/Try.h +1/−15
- folly/folly/Unicode.cpp +22/−21
- folly/folly/Unicode.h +8/−1
- folly/folly/Uri-inl.h +6/−6
- folly/folly/Uri.cpp +1/−1
- folly/folly/Utility.h +78/−12
- folly/folly/VirtualExecutor.h +1/−1
- folly/folly/algorithm/simd/Contains.h +1/−1
- folly/folly/algorithm/simd/Ignore.h +1/−1
- folly/folly/algorithm/simd/detail/SimdAnyOf.h +1/−1
- folly/folly/algorithm/simd/detail/SimdForEach.h +5/−3
- folly/folly/algorithm/simd/detail/SimdPlatform.h +4/−4
- folly/folly/algorithm/simd/detail/UnrollUtils.h +8/−9
- folly/folly/base64.h +48/−6
- folly/folly/channels/Channel-inl.h +8/−7
- folly/folly/channels/ChannelProcessor-inl.h +4/−4
- folly/folly/channels/ConsumeChannel-inl.h +3/−2
- folly/folly/channels/Merge-inl.h +3/−2
- folly/folly/channels/MergeChannel-inl.h +22/−16
- folly/folly/channels/MultiplexChannel-inl.h +5/−4
- folly/folly/channels/ProxyChannel.h +2/−0
- folly/folly/channels/Transform-inl.h +1/−1
- folly/folly/channels/detail/PointerVariant.h +8/−7
- folly/folly/channels/detail/Utility.h +2/−2
- folly/folly/chrono/Conv.h +6/−6
- folly/folly/cli/NestedCommandLineApp.cpp +27/−13
- folly/folly/cli/NestedCommandLineApp.h +9/−2
- folly/folly/cli/ProgramOptions.cpp +2/−3
- folly/folly/compression/Compression.cpp +24/−18
- folly/folly/compression/CompressionContextPool.h +155/−27
- folly/folly/compression/CompressionContextPoolSingletons.cpp +77/−6
- folly/folly/compression/CompressionContextPoolSingletons.h +44/−2
- folly/folly/compression/CompressionCoreLocalContextPool.h +82/−13
- folly/folly/compression/QuotientMultiSet-inl.h +2/−2
- folly/folly/compression/Zlib.cpp +10/−6
- folly/folly/compression/Zstd.cpp +0/−2
- folly/folly/compression/elias_fano/BitVectorCoding.h +10/−10
- folly/folly/compression/elias_fano/EliasFanoCoding.h +1/−1
- folly/folly/concurrency/AtomicSharedPtr.h +7/−7
- folly/folly/concurrency/CacheLocality.cpp +98/−24
- folly/folly/concurrency/CacheLocality.h +24/−12
- folly/folly/concurrency/ConcurrentHashMap.h +26/−19
- folly/folly/concurrency/SingletonRelaxedCounter.h +5/−4
- folly/folly/concurrency/ThreadCachedSynchronized.h +1/−1
- folly/folly/concurrency/container/FlatCombiningPriorityQueue.h +3/−6
- folly/folly/concurrency/container/LockFreeRingBuffer.h +4/−8
- folly/folly/concurrency/container/RelaxedConcurrentPriorityQueue.h +6/−4
- folly/folly/concurrency/container/atomic_grow_array.h +50/−4
- folly/folly/concurrency/detail/AtomicSharedPtr-detail.h +10/−10
- folly/folly/concurrency/detail/ConcurrentHashMap-detail.h +124/−78
- folly/folly/concurrency/memory/PrimaryPtr.h +3/−3
- folly/folly/container/CollectionUtil.h +67/−0
- folly/folly/container/EvictingCacheMap.h +12/−4
- folly/folly/container/F14Map.h +48/−56
- folly/folly/container/F14Set.h +60/−72
- folly/folly/container/FBVector.h +1/−1
- folly/folly/container/HeterogeneousAccess.h +60/−2
- folly/folly/container/IntrusiveHeap.h +3/−2
- folly/folly/container/IntrusiveList.h +3/−3
- folly/folly/container/Iterator.h +1/−2
- folly/folly/container/MapUtil.h +16/−3
- folly/folly/container/Merge.h +1/−1
- folly/folly/container/RegexMatchCache.cpp +7/−15
- folly/folly/container/RegexMatchCache.h +7/−47
- folly/folly/container/StdBitset.h +83/−14
- folly/folly/container/detail/F14Policy.h +14/−4
- folly/folly/container/detail/F14Table.h +70/−45
- folly/folly/container/detail/Util.h +4/−4
- folly/folly/container/heap_vector_types.h +10/−5
- folly/folly/container/irange.h +70/−0
- folly/folly/container/small_vector.h +77/−53
- folly/folly/container/sorted_vector_types.h +52/−47
- folly/folly/container/span.h +2/−3
- folly/folly/container/tape.h +11/−14
- folly/folly/container/test/TrackingTypes.h +1/−1
- folly/folly/coro/AsyncGenerator.h +59/−123
- folly/folly/coro/AsyncPipe.h +1/−1
- folly/folly/coro/AsyncScope.h +14/−8
- folly/folly/coro/AsyncStack.h +1/−1
- folly/folly/coro/AwaitImmediately.h +0/−239
- folly/folly/coro/AwaitResult.h +0/−158
- folly/folly/coro/BasePromise.h +147/−0
- folly/folly/coro/BlockingWait.h +37/−14
- folly/folly/coro/Collect-inl.h +87/−71
- folly/folly/coro/Collect.h +18/−17
- folly/folly/coro/Coroutine.h +119/−27
- folly/folly/coro/Error.h +66/−0
- folly/folly/coro/FutureUtil.h +1/−1
- folly/folly/coro/GmockHelpers.h +118/−1
- folly/folly/coro/GtestHelpers.h +6/−0
- folly/folly/coro/Noexcept.h +0/−299
- folly/folly/coro/Nothrow.h +166/−0
- folly/folly/coro/Promise.h +24/−3
- folly/folly/coro/Ready.h +0/−114
- folly/folly/coro/Result.h +14/−38
- folly/folly/coro/Retry.h +4/−4
- folly/folly/coro/RustAdaptors.h +5/−0
- folly/folly/coro/ScopeExit.h +3/−2
- folly/folly/coro/SharedLock.h +1/−1
- folly/folly/coro/SharedMutex.h +7/−1
- folly/folly/coro/SharedPromise.h +14/−0
- folly/folly/coro/SmallUnboundedQueue.h +1/−1
- folly/folly/coro/Synchronized.h +2/−2
- folly/folly/coro/Task.h +63/−136
- folly/folly/coro/TaskWrapper.h +172/−70
- folly/folly/coro/Timeout-inl.h +4/−4
- folly/folly/coro/Timeout.h +9/−7
- folly/folly/coro/UnboundedQueue.h +13/−2
- folly/folly/coro/ValueOrError.h +235/−0
- folly/folly/coro/ValueOrFatal.h +337/−0
- folly/folly/coro/ViaIfAsync.h +163/−152
- folly/folly/coro/WithAsyncStack.h +20/−6
- folly/folly/coro/WithCancellation.h +23/−14
- folly/folly/coro/detail/PickTaskWrapper.h +42/−39
- folly/folly/coro/safe/AsyncClosure.h +22/−23
- folly/folly/coro/safe/BindCaptures.h +159/−0
- folly/folly/coro/safe/Captures.h +101/−232
- folly/folly/coro/safe/NowTask.h +81/−44
- folly/folly/coro/safe/SafeAlias.h +0/−203
- folly/folly/coro/safe/SafeTask.h +214/−158
- folly/folly/coro/safe/detail/AsyncClosure.h +210/−130
- folly/folly/coro/safe/detail/AsyncClosureBindings.h +0/−773
- folly/folly/coro/safe/detail/BindAsyncClosure.h +878/−0
- folly/folly/debugging/exception_tracer/ExceptionCounterLib.cpp +4/−14
- folly/folly/debugging/exception_tracer/ExceptionCounterLib.h +7/−0
- folly/folly/debugging/exception_tracer/ExceptionCounterLibStaticRegistration.cpp +35/−0
- folly/folly/debugging/exception_tracer/ExceptionStackTraceLib.cpp +40/−6
- folly/folly/debugging/exception_tracer/ExceptionTracer.cpp +9/−5
- folly/folly/debugging/exception_tracer/ExceptionTracerLib.cpp +11/−4
- folly/folly/debugging/exception_tracer/ExceptionTracerLib.h +5/−0
- folly/folly/debugging/exception_tracer/SmartExceptionStackTraceHooks.cpp +21/−23
- folly/folly/debugging/exception_tracer/SmartExceptionTracer.cpp +22/−31
- folly/folly/debugging/exception_tracer/SmartExceptionTracerSingleton.h +4/−3
- folly/folly/debugging/symbolizer/Dwarf.cpp +1/−1
- folly/folly/debugging/symbolizer/Dwarf.h +3/−3
- folly/folly/debugging/symbolizer/DwarfImpl.cpp +1/−1
- folly/folly/debugging/symbolizer/DwarfImpl.h +3/−3
- folly/folly/debugging/symbolizer/DwarfLineNumberVM.h +1/−1
- folly/folly/debugging/symbolizer/DwarfUtil.cpp +2/−2
- folly/folly/debugging/symbolizer/DwarfUtil.h +3/−3
- folly/folly/debugging/symbolizer/Elf-inl.h +136/−16
- folly/folly/debugging/symbolizer/Elf.cpp +51/−20
- folly/folly/debugging/symbolizer/Elf.h +195/−15
- folly/folly/debugging/symbolizer/ElfCache.h +13/−2
- folly/folly/debugging/symbolizer/SignalHandler.cpp +70/−27
- folly/folly/debugging/symbolizer/SignalHandler.h +1/−1
- folly/folly/debugging/symbolizer/StackTrace.cpp +44/−10
- folly/folly/debugging/symbolizer/SymbolizePrinter.h +1/−1
- folly/folly/debugging/symbolizer/Symbolizer.cpp +63/−25
- folly/folly/debugging/symbolizer/Symbolizer.h +54/−15
- folly/folly/debugging/symbolizer/detail/Debug.cpp +0/−4
- folly/folly/detail/AsyncTrace.cpp +0/−2
- folly/folly/detail/AtomicUnorderedMapUtils.h +16/−44
- folly/folly/detail/FileUtilDetail.h +4/−3
- folly/folly/detail/GroupVarintDetail.h +3/−3
- folly/folly/detail/IPAddress.cpp +5/−4
- folly/folly/detail/IPAddressSource.h +5/−4
- folly/folly/detail/MPMCPipelineDetail.h +4/−4
- folly/folly/detail/MemoryIdler.cpp +1/−3
- folly/folly/detail/MemoryIdler.h +4/−3
- folly/folly/detail/PerfScoped.cpp +23/−2
- folly/folly/detail/PolyDetail.h +7/−5
- folly/folly/detail/SplitStringSimd.cpp +0/−84
- folly/folly/detail/SplitStringSimd.h +9/−119
- folly/folly/detail/SplitStringSimdImpl.h +16/−17
- folly/folly/detail/ThreadLocalDetail.cpp +83/−11
- folly/folly/detail/ThreadLocalDetail.h +111/−6
- folly/folly/detail/base64_detail/Base64Api.h +14/−0
- folly/folly/detail/base64_detail/Base64Common.h +10/−0
- folly/folly/detail/base64_detail/Base64Constants.h +19/−0
- folly/folly/detail/base64_detail/Base64Scalar.h +23/−5
- folly/folly/detail/tuple.h +6/−6
- folly/folly/dynamic-inl.h +1/−1
- folly/folly/dynamic.h +1/−1
- folly/folly/executors/CPUThreadPoolExecutor.cpp +54/−83
- folly/folly/executors/CPUThreadPoolExecutor.h +55/−34
- folly/folly/executors/Codel.cpp +4/−3
- folly/folly/executors/EDFThreadPoolExecutor.cpp +65/−114
- folly/folly/executors/EDFThreadPoolExecutor.h +4/−10
- folly/folly/executors/ExecutionObserver.h +2/−3
- folly/folly/executors/FunctionScheduler.cpp +24/−6
- folly/folly/executors/FunctionScheduler.h +5/−0
- folly/folly/executors/GlobalExecutor.cpp +2/−2
- folly/folly/executors/IOObjectCache.h +1/−1
- folly/folly/executors/IOThreadPoolExecutor.cpp +8/−4
- folly/folly/executors/ManualExecutor.h +16/−3
- folly/folly/executors/ScheduledExecutor.h +2/−2
- folly/folly/executors/SerialExecutor.h +4/−29
- folly/folly/executors/SoftRealTimeExecutor.h +2/−6
- folly/folly/executors/StrandExecutor.cpp +12/−10
- folly/folly/executors/StripedEDFThreadPoolExecutor.cpp +209/−0
- folly/folly/executors/StripedEDFThreadPoolExecutor.h +67/−0
- folly/folly/executors/ThreadPoolExecutor.cpp +13/−2
- folly/folly/executors/ThreadPoolExecutor.h +15/−2
- folly/folly/executors/task_queue/BlockingQueue.h +2/−2
- folly/folly/executors/task_queue/LifoSemMPMCQueue.h +6/−6
- folly/folly/executors/task_queue/PriorityLifoSemMPMCQueue.h +7/−21
- folly/folly/executors/task_queue/PriorityUnboundedBlockingQueue.h +3/−5
- folly/folly/executors/task_queue/StripedPriorityUnboundedBlockingQueue.h +131/−0
- folly/folly/executors/task_queue/UnboundedBlockingQueue.h +2/−2
- folly/folly/executors/thread_factory/InitThreadFactory.h +3/−2
- folly/folly/experimental/EventCount.h +1/−1
- folly/folly/experimental/FlatCombiningPriorityQueue.h +1/−1
- folly/folly/experimental/FunctionScheduler.h +1/−1
- folly/folly/experimental/TestUtil.h +0/−17
- folly/folly/experimental/ThreadedRepeatingFunctionRunner.h +1/−1
- folly/folly/experimental/channels/Channel-fwd.h +1/−1
- folly/folly/experimental/channels/Channel-inl.h +1/−1
- folly/folly/experimental/channels/Channel.h +1/−1
- folly/folly/experimental/channels/ChannelCallbackHandle.h +1/−1
- folly/folly/experimental/channels/ChannelProcessor-inl.h +1/−1
- folly/folly/experimental/channels/ChannelProcessor.h +1/−1
- folly/folly/experimental/channels/ConsumeChannel-inl.h +1/−1
- folly/folly/experimental/channels/ConsumeChannel.h +1/−1
- folly/folly/experimental/channels/FanoutChannel-inl.h +1/−1
- folly/folly/experimental/channels/FanoutChannel.h +1/−1
- folly/folly/experimental/channels/FanoutSender-inl.h +1/−1
- folly/folly/experimental/channels/FanoutSender.h +1/−1
- folly/folly/experimental/channels/MaxConcurrentRateLimiter.h +1/−1
- folly/folly/experimental/channels/Merge-inl.h +1/−1
- folly/folly/experimental/channels/Merge.h +1/−1
- folly/folly/experimental/channels/MergeChannel-inl.h +1/−1
- folly/folly/experimental/channels/MergeChannel.h +1/−1
- folly/folly/experimental/channels/MultiplexChannel-inl.h +1/−1
- folly/folly/experimental/channels/MultiplexChannel.h +1/−1
- folly/folly/experimental/channels/OnClosedException.h +1/−1
- folly/folly/experimental/channels/Producer-inl.h +1/−1
- folly/folly/experimental/channels/Producer.h +1/−1
- folly/folly/experimental/channels/ProxyChannel-inl.h +1/−1
- folly/folly/experimental/channels/ProxyChannel.h +1/−1
- folly/folly/experimental/channels/RateLimiter.h +1/−1
- folly/folly/experimental/channels/Transform-inl.h +1/−1
- folly/folly/experimental/channels/Transform.h +1/−1
- folly/folly/experimental/channels/detail/AtomicQueue.h +1/−1
- folly/folly/experimental/channels/detail/ChannelBridge.h +1/−1
- folly/folly/experimental/channels/detail/FunctionTraits.h +1/−1
- folly/folly/experimental/channels/detail/IntrusivePtr.h +1/−1
- folly/folly/experimental/channels/detail/MultiplexerTraits.h +1/−1
- folly/folly/experimental/channels/detail/PointerVariant.h +1/−1
- folly/folly/experimental/channels/detail/Utility.h +1/−1
- folly/folly/experimental/coro/AsyncGenerator.h +1/−1
- folly/folly/experimental/coro/AsyncPipe.h +1/−1
- folly/folly/experimental/coro/AsyncScope.h +1/−1
- folly/folly/experimental/coro/AsyncStack.h +1/−1
- folly/folly/experimental/coro/AutoCleanup-fwd.h +1/−1
- folly/folly/experimental/coro/AutoCleanup.h +1/−1
- folly/folly/experimental/coro/Baton.h +1/−1
- folly/folly/experimental/coro/BlockingWait.h +1/−1
- folly/folly/experimental/coro/BoundedQueue.h +1/−1
- folly/folly/experimental/coro/Cleanup.h +1/−1
- folly/folly/experimental/coro/Collect-inl.h +1/−1
- folly/folly/experimental/coro/Collect.h +1/−1
- folly/folly/experimental/coro/Concat-inl.h +1/−1
- folly/folly/experimental/coro/Concat.h +1/−1
- folly/folly/experimental/coro/Coroutine.h +1/−1
- folly/folly/experimental/coro/CurrentExecutor.h +1/−1
- folly/folly/experimental/coro/DetachOnCancel.h +1/−1
- folly/folly/experimental/coro/Filter-inl.h +1/−1
- folly/folly/experimental/coro/Filter.h +1/−1
- folly/folly/experimental/coro/FutureUtil.h +1/−1
- folly/folly/experimental/coro/Generator.h +1/−1
- folly/folly/experimental/coro/GmockHelpers.h +1/−1
- folly/folly/experimental/coro/GtestHelpers.h +1/−1
- folly/folly/experimental/coro/Invoke.h +1/−1
- folly/folly/experimental/coro/Merge-inl.h +1/−1
- folly/folly/experimental/coro/Merge.h +1/−1
- folly/folly/experimental/coro/Mutex.h +1/−1
- folly/folly/experimental/coro/Promise.h +1/−1
- folly/folly/experimental/coro/Result.h +1/−1
- folly/folly/experimental/coro/Retry.h +1/−1
- folly/folly/experimental/coro/RustAdaptors.h +1/−1
- folly/folly/experimental/coro/ScopeExit.h +1/−1
- folly/folly/experimental/coro/SharedLock.h +1/−1
- folly/folly/experimental/coro/SharedMutex.h +1/−1
- folly/folly/experimental/coro/SharedPromise.h +1/−1
- folly/folly/experimental/coro/Sleep-inl.h +1/−1
- folly/folly/experimental/coro/Sleep.h +1/−1
- folly/folly/experimental/coro/SmallUnboundedQueue.h +1/−1
- folly/folly/experimental/coro/Task.h +1/−1
- folly/folly/experimental/coro/TimedWait.h +1/−1
- folly/folly/experimental/coro/Timeout-inl.h +1/−1
- folly/folly/experimental/coro/Timeout.h +1/−1
- folly/folly/experimental/coro/Traits.h +1/−1
- folly/folly/experimental/coro/Transform-inl.h +1/−1
- folly/folly/experimental/coro/Transform.h +1/−1
- folly/folly/experimental/coro/UnboundedQueue.h +1/−1
- folly/folly/experimental/coro/ViaIfAsync.h +1/−1
- folly/folly/experimental/coro/WithAsyncStack.h +1/−1
- folly/folly/experimental/coro/WithCancellation.h +1/−1
- folly/folly/experimental/coro/detail/Barrier.h +1/−1
- folly/folly/experimental/coro/detail/BarrierTask.h +1/−1
- folly/folly/experimental/coro/detail/CurrentAsyncFrame.h +1/−1
- folly/folly/experimental/coro/detail/Helpers.h +1/−1
- folly/folly/experimental/coro/detail/InlineTask.h +1/−1
- folly/folly/experimental/coro/detail/Malloc.h +1/−1
- folly/folly/experimental/coro/detail/ManualLifetime.h +1/−1
- folly/folly/experimental/coro/detail/Traits.h +1/−1
- folly/folly/experimental/crypto/Blake2xb.h +1/−1
- folly/folly/experimental/crypto/LtHash.h +1/−1
- folly/folly/experimental/exception_tracer/ExceptionAbi.h +1/−1
- folly/folly/experimental/exception_tracer/ExceptionCounterLib.h +1/−1
- folly/folly/experimental/exception_tracer/ExceptionTracer.h +1/−1
- folly/folly/experimental/exception_tracer/ExceptionTracerLib.h +1/−1
- folly/folly/experimental/exception_tracer/SmartExceptionTracer.h +1/−1
- folly/folly/experimental/exception_tracer/SmartExceptionTracerSingleton.h +1/−1
- folly/folly/experimental/exception_tracer/StackTrace.h +1/−1
- folly/folly/experimental/flat_combining/FlatCombining.h +1/−1
- folly/folly/experimental/io/AsyncBase.h +1/−1
- folly/folly/experimental/io/AsyncIO.h +1/−1
- folly/folly/experimental/io/AsyncIoUringSocket.h +1/−1
- folly/folly/experimental/io/AsyncIoUringSocketFactory.h +1/−1
- folly/folly/experimental/io/Epoll.h +1/−1
- folly/folly/experimental/io/EpollBackend.h +1/−1
- folly/folly/experimental/io/EventBasePoller.h +1/−1
- folly/folly/experimental/io/FsUtil.h +1/−1
- folly/folly/experimental/io/HugePages.h +1/−1
- folly/folly/experimental/io/IoUring.h +1/−1
- folly/folly/experimental/io/IoUringBackend.h +1/−1
- folly/folly/experimental/io/IoUringBase.h too large to diff
- folly/folly/experimental/io/IoUringEvent.h too large to diff
- folly/folly/experimental/io/IoUringEventBaseLocal.h too large to diff
- folly/folly/experimental/io/IoUringProvidedBufferRing.h too large to diff
- folly/folly/experimental/io/Liburing.h too large to diff
- folly/folly/experimental/io/MuxIOThreadPoolExecutor.h too large to diff
- folly/folly/experimental/io/SimpleAsyncIO.h too large to diff
- folly/folly/experimental/observer/CoreCachedObserver.h too large to diff
- folly/folly/experimental/observer/HazptrObserver.h too large to diff
- folly/folly/experimental/observer/Observable-inl.h too large to diff
- folly/folly/experimental/observer/Observable.h too large to diff
- folly/folly/experimental/observer/Observer-inl.h too large to diff
- folly/folly/experimental/observer/Observer-pre.h too large to diff
- folly/folly/experimental/observer/Observer.h too large to diff
- folly/folly/experimental/observer/ReadMostlyTLObserver.h too large to diff
- folly/folly/experimental/observer/SimpleObservable-inl.h too large to diff
- folly/folly/experimental/observer/SimpleObservable.h too large to diff
- folly/folly/experimental/observer/WithJitter-inl.h too large to diff
- folly/folly/experimental/observer/WithJitter.h too large to diff
- folly/folly/experimental/observer/detail/Core.h too large to diff
- folly/folly/experimental/observer/detail/GraphCycleDetector.h too large to diff
- folly/folly/experimental/observer/detail/ObserverManager.h too large to diff
- folly/folly/experimental/settings/Immutables.h too large to diff
- folly/folly/experimental/settings/Settings.h too large to diff
- folly/folly/experimental/settings/Types.h too large to diff
- folly/folly/experimental/settings/detail/SettingsImpl.h too large to diff
- folly/folly/experimental/symbolizer/Dwarf.h too large to diff
- folly/folly/experimental/symbolizer/DwarfImpl.h too large to diff
- folly/folly/experimental/symbolizer/DwarfLineNumberVM.h too large to diff
- folly/folly/experimental/symbolizer/DwarfSection.h too large to diff
- folly/folly/experimental/symbolizer/DwarfUtil.h too large to diff
- folly/folly/experimental/symbolizer/Elf-inl.h too large to diff
- folly/folly/experimental/symbolizer/Elf.h too large to diff
- folly/folly/experimental/symbolizer/ElfCache.h too large to diff
- folly/folly/experimental/symbolizer/LineReader.h too large to diff
- folly/folly/experimental/symbolizer/SignalHandler.h too large to diff
- folly/folly/experimental/symbolizer/StackTrace.h too large to diff
- folly/folly/experimental/symbolizer/SymbolizePrinter.h too large to diff
- folly/folly/experimental/symbolizer/SymbolizedFrame.h too large to diff
- folly/folly/experimental/symbolizer/Symbolizer.h too large to diff
- folly/folly/experimental/symbolizer/detail/Debug.h too large to diff
- folly/folly/ext/buck2/test_ext.cpp too large to diff
- folly/folly/ext/test_ext.h too large to diff
- folly/folly/external/fast-crc32/avx512_crc32c_v8s3x4.h too large to diff
- folly/folly/external/fast-crc32/sse_crc32c_v8s3x3.h too large to diff
- folly/folly/fibers/AddTasks.h too large to diff
- folly/folly/fibers/AtomicBatchDispatcher-inl.h too large to diff
- folly/folly/fibers/Baton.h too large to diff
- folly/folly/fibers/Fiber.cpp too large to diff
- folly/folly/fibers/FiberManager.cpp too large to diff
- folly/folly/fibers/FiberManagerInternal.h too large to diff
- folly/folly/fibers/ForEach-inl.h too large to diff
- folly/folly/fibers/GenericBaton.h too large to diff
- folly/folly/fibers/GuardPageAllocator.cpp too large to diff
- folly/folly/fibers/GuardPageAllocator.h too large to diff
- folly/folly/fibers/LoopController.h too large to diff
- folly/folly/fibers/Promise-inl.h too large to diff
- folly/folly/fibers/Promise.h too large to diff
- folly/folly/fibers/Semaphore.cpp too large to diff
- folly/folly/fibers/SemaphoreBase.cpp too large to diff
- folly/folly/fibers/TimedMutex-inl.h too large to diff
- folly/folly/fibers/TimedMutex.h too large to diff
- folly/folly/fibers/WhenN-inl.h too large to diff
- folly/folly/fibers/async/Async.h too large to diff
- folly/folly/fibers/async/Collect-inl.h too large to diff
- folly/folly/fibers/traits.h too large to diff
- folly/folly/functional/ApplyTuple.h too large to diff
- folly/folly/functional/Invoke.h too large to diff
- folly/folly/functional/Partial.h too large to diff
- folly/folly/functional/protocol.h too large to diff
- folly/folly/functional/traits.h too large to diff
- folly/folly/futures/Barrier.h too large to diff
- folly/folly/futures/Future-inl.h too large to diff
- folly/folly/futures/Future.cpp too large to diff
- folly/folly/futures/Future.h too large to diff
- folly/folly/futures/HeapTimekeeper.cpp too large to diff
- folly/folly/futures/HeapTimekeeper.h too large to diff
- folly/folly/futures/Portability.h too large to diff
- folly/folly/futures/Retrying.h too large to diff
- folly/folly/futures/WTCallback.h too large to diff
- folly/folly/futures/detail/Core.h too large to diff
- folly/folly/gen/Base-inl.h too large to diff
- folly/folly/gen/Base.h too large to diff
- folly/folly/gen/Combine-inl.h too large to diff
- folly/folly/gen/ParallelMap-inl.h too large to diff
- folly/folly/hash/Checksum.cpp too large to diff
- folly/folly/hash/Checksum.h too large to diff
- folly/folly/hash/FnvHash.h too large to diff
- folly/folly/hash/Hash.h too large to diff
- folly/folly/hash/HsiehHash.h too large to diff
- folly/folly/hash/SpookyHashV1.cpp too large to diff
- folly/folly/hash/SpookyHashV2.cpp too large to diff
- folly/folly/hash/UniqueHashKey.cpp too large to diff
- folly/folly/hash/UniqueHashKey.h too large to diff
- folly/folly/hash/detail/ChecksumDetail.cpp too large to diff
- folly/folly/hash/detail/ChecksumDetail.h too large to diff
- folly/folly/hash/detail/Crc32CombineDetail.cpp too large to diff
- folly/folly/hash/detail/Crc32cDetail.cpp too large to diff
- folly/folly/hash/detail/RandomSeed.h too large to diff
- folly/folly/hash/traits.h too large to diff
- folly/folly/init/Init.cpp too large to diff
- folly/folly/init/Init.h too large to diff
- folly/folly/io/Cursor.h too large to diff
- folly/folly/io/FsUtil.cpp too large to diff
- folly/folly/io/FsUtil.h too large to diff
- folly/folly/io/HugePages.cpp too large to diff
- folly/folly/io/HugePages.h too large to diff
- folly/folly/io/IOBuf.cpp too large to diff
- folly/folly/io/IOBuf.h too large to diff
- folly/folly/io/IOBufQueue.cpp too large to diff
- folly/folly/io/RecordIO.cpp too large to diff
- folly/folly/io/RecordIO.h too large to diff
- folly/folly/io/ShutdownSocketSet.cpp too large to diff
- folly/folly/io/TypedIOBuf.h too large to diff
- folly/folly/io/async/AsyncBase.cpp too large to diff
- folly/folly/io/async/AsyncIoUringSocket.cpp too large to diff
- folly/folly/io/async/AsyncIoUringSocket.h too large to diff
- folly/folly/io/async/AsyncIoUringSocketFactory.cpp too large to diff
- folly/folly/io/async/AsyncIoUringSocketFactory.h too large to diff
- folly/folly/io/async/AsyncPipe.cpp too large to diff
- folly/folly/io/async/AsyncSSLSocket.cpp too large to diff
- folly/folly/io/async/AsyncSSLSocket.h too large to diff
- folly/folly/io/async/AsyncServerSocket.cpp too large to diff
- folly/folly/io/async/AsyncServerSocket.h too large to diff
- folly/folly/io/async/AsyncSignalHandler.cpp too large to diff
- folly/folly/io/async/AsyncSocket.cpp too large to diff
- folly/folly/io/async/AsyncSocket.h too large to diff
- folly/folly/io/async/AsyncSocketException.h too large to diff
- folly/folly/io/async/AsyncSocketTransport.cpp too large to diff
- folly/folly/io/async/AsyncSocketTransport.h too large to diff
- folly/folly/io/async/AsyncTimeout.h too large to diff
- folly/folly/io/async/AsyncTransport.h too large to diff
- folly/folly/io/async/AsyncUDPServerSocket.h too large to diff
- folly/folly/io/async/CertificateIdentityVerifier.h too large to diff
- folly/folly/io/async/DelayedDestructionBase.h too large to diff
- folly/folly/io/async/EpollBackend.cpp too large to diff
- folly/folly/io/async/EventBase.cpp too large to diff
- folly/folly/io/async/EventBase.h too large to diff
- folly/folly/io/async/EventBaseAtomicNotificationQueue.h too large to diff
- folly/folly/io/async/EventBaseBackendBase.cpp too large to diff
- folly/folly/io/async/EventBaseBackendBase.h too large to diff
- folly/folly/io/async/EventBaseLocal.cpp too large to diff
- folly/folly/io/async/EventBasePoller.cpp too large to diff
- folly/folly/io/async/EventBaseThread.cpp too large to diff
- folly/folly/io/async/EventHandler.cpp too large to diff
- folly/folly/io/async/EventHandler.h too large to diff
- folly/folly/io/async/EventUtil.h too large to diff
- folly/folly/io/async/HHWheelTimer.cpp too large to diff
- folly/folly/io/async/HHWheelTimer.h too large to diff
- folly/folly/io/async/IoUring.cpp too large to diff
- folly/folly/io/async/IoUringBackend.cpp too large to diff
- folly/folly/io/async/IoUringBackend.h too large to diff
- folly/folly/io/async/IoUringBase.h too large to diff
- folly/folly/io/async/IoUringConnect.cpp too large to diff
- folly/folly/io/async/IoUringConnect.h too large to diff
- folly/folly/io/async/IoUringEvent.cpp too large to diff
- folly/folly/io/async/IoUringEvent.h too large to diff
- folly/folly/io/async/IoUringEventBaseLocal.cpp too large to diff
- folly/folly/io/async/IoUringEventBaseLocal.h too large to diff
- folly/folly/io/async/IoUringProvidedBufferRing.cpp too large to diff
- folly/folly/io/async/IoUringProvidedBufferRing.h too large to diff
- folly/folly/io/async/IoUringRecv.cpp too large to diff
- folly/folly/io/async/IoUringRecv.h too large to diff
- folly/folly/io/async/IoUringSend.cpp too large to diff
- folly/folly/io/async/IoUringSend.h too large to diff
- folly/folly/io/async/IoUringZeroCopyBufferPool.cpp too large to diff
- folly/folly/io/async/IoUringZeroCopyBufferPool.h too large to diff
- folly/folly/io/async/MuxIOThreadPoolExecutor.cpp too large to diff
- folly/folly/io/async/NotificationQueue.h too large to diff
- folly/folly/io/async/Request.cpp too large to diff
- folly/folly/io/async/Request.h too large to diff
- folly/folly/io/async/SSLContext.cpp too large to diff
- folly/folly/io/async/STTimerFDTimeoutManager.h too large to diff
- folly/folly/io/async/ScopedEventBaseThread.cpp too large to diff
- folly/folly/io/async/ScopedEventBaseThread.h too large to diff
- folly/folly/io/async/SimpleAsyncIO.cpp too large to diff
- folly/folly/io/async/TimeoutManager.h too large to diff
- folly/folly/io/async/TimerFD.cpp too large to diff
- folly/folly/io/async/TimerFD.h too large to diff
- folly/folly/io/async/TimerFDTimeoutManager.h too large to diff
- folly/folly/io/async/fdsock/AsyncFdSocket.cpp too large to diff
- folly/folly/io/async/observer/AsyncSocketObserverInterface.h too large to diff
- folly/folly/io/async/ssl/SSLErrors.cpp too large to diff
- folly/folly/io/async/ssl/SSLErrors.h too large to diff
- folly/folly/io/async/test/AsyncSSLSocketTest.h too large to diff
- folly/folly/io/async/test/AsyncSocketTest.h too large to diff
- folly/folly/io/async/test/AsyncSocketTest2.h too large to diff
- folly/folly/io/async/test/MockAsyncServerSocket.h too large to diff
- folly/folly/io/async/test/MockAsyncSocket.h too large to diff
- folly/folly/io/async/test/MockAsyncTransport.h too large to diff
- folly/folly/io/async/test/ScopedBoundPort.cpp too large to diff
- folly/folly/io/async/test/SocketPair.cpp too large to diff
- folly/folly/io/async/test/TimeUtil.cpp too large to diff
- folly/folly/io/coro/ServerSocket.cpp too large to diff
- folly/folly/io/coro/ServerSocket.h too large to diff
- folly/folly/io/coro/TransportCallbackBase.h too large to diff
- folly/folly/json.h too large to diff
- folly/folly/json/DynamicConverter.h too large to diff
- folly/folly/json/DynamicParser-inl.h too large to diff
- folly/folly/json/DynamicParser.h too large to diff
- folly/folly/json/JSONSchema.cpp too large to diff
- folly/folly/json/bser/Dump.cpp too large to diff
- folly/folly/json/bser/Load.cpp too large to diff
- folly/folly/json/dynamic-inl.h too large to diff
- folly/folly/json/dynamic.h too large to diff
- folly/folly/json/json.cpp too large to diff
- folly/folly/json/json_pointer.cpp too large to diff
- folly/folly/json_patch.h too large to diff
- folly/folly/json_pointer.h too large to diff
- folly/folly/lang/Align.h too large to diff
- folly/folly/lang/Bindings.h too large to diff
- folly/folly/lang/Bits.h too large to diff
- folly/folly/lang/BitsClass.h too large to diff
- folly/folly/lang/Exception.cpp too large to diff
- folly/folly/lang/Exception.h too large to diff
- folly/folly/lang/Hint-inl.h too large to diff
- folly/folly/lang/MustUseImmediately.h too large to diff
- folly/folly/lang/New.h too large to diff
- folly/folly/lang/SafeAlias-fwd.h too large to diff
- folly/folly/lang/SafeAlias.h too large to diff
- folly/folly/lang/SafeAssert.cpp too large to diff
- folly/folly/lang/SafeAssert.h too large to diff
- folly/folly/lang/SafeClosure.h too large to diff
- folly/folly/lang/Strong.h too large to diff
- folly/folly/lang/Switch.h too large to diff
- folly/folly/lang/ToAscii.h too large to diff
- folly/folly/lang/UncaughtExceptions.h too large to diff
- folly/folly/lang/bind/AsArgument.h too large to diff
- folly/folly/lang/bind/Bind.h too large to diff
- folly/folly/lang/bind/Named.h too large to diff
- folly/folly/lang/bind/NamedToStorage.h too large to diff
- folly/folly/lang/bind/ToStorage.h too large to diff
- folly/folly/lang/cstring_view.h too large to diff
- folly/folly/lang/named/Bindings.h too large to diff
- folly/folly/logging/AutoTimer.h too large to diff
- folly/folly/logging/CustomLogFormatter.cpp too large to diff
- folly/folly/logging/FileWriterFactory.cpp too large to diff
- folly/folly/logging/Init.cpp too large to diff
- folly/folly/logging/LogConfig.cpp too large to diff
- folly/folly/logging/LogConfigParser.cpp too large to diff
- folly/folly/logging/LogLevel.cpp too large to diff
- folly/folly/logging/LogStreamProcessor.cpp too large to diff
- folly/folly/logging/LogStreamProcessor.h too large to diff
- folly/folly/logging/Logger.cpp too large to diff
- folly/folly/logging/LoggerDB.cpp too large to diff
- folly/folly/logging/ObjectToString.h too large to diff
- folly/folly/logging/StandardLogHandlerFactory.cpp too large to diff
- folly/folly/logging/StreamHandlerFactory.cpp too large to diff
- folly/folly/logging/xlog.cpp too large to diff
- folly/folly/logging/xlog.h too large to diff
- folly/folly/memcpy.S too large to diff
- folly/folly/memcpy_select_aarch64.cpp too large to diff
- folly/folly/memory/IoUringArena.cpp too large to diff
- folly/folly/memory/IoUringArena.h too large to diff
- folly/folly/memory/JemallocNodumpAllocator.cpp too large to diff
- folly/folly/memory/Malloc.cpp too large to diff
- folly/folly/memory/Malloc.h too large to diff
- folly/folly/memory/ReentrantAllocator.h too large to diff
- folly/folly/memory/UninitializedMemoryHacks.h too large to diff
- folly/folly/memory/not_null-inl.h too large to diff
- folly/folly/memory/not_null.h too large to diff
- folly/folly/memset_select_aarch64.cpp too large to diff
- folly/folly/net/NetOps.cpp too large to diff
- folly/folly/net/NetOps.h too large to diff
- folly/folly/net/NetOpsDispatcher.cpp too large to diff
- folly/folly/net/NetOpsDispatcher.h too large to diff
- folly/folly/net/TcpInfo.h too large to diff
- folly/folly/observer/CoreCachedObserver.h too large to diff
- folly/folly/observer/HazptrObserver.h too large to diff
- folly/folly/observer/ReadMostlyTLObserver.h too large to diff
- folly/folly/observer/WithJitter-inl.h too large to diff
- folly/folly/observer/detail/Core.h too large to diff
- folly/folly/observer/detail/ObserverManager.cpp too large to diff
- folly/folly/portability/Filesystem.cpp too large to diff
- folly/folly/portability/Filesystem.h too large to diff
- folly/folly/portability/GFlags.h too large to diff
- folly/folly/portability/Stdlib.h too large to diff
- folly/folly/portability/SysMman.cpp too large to diff
- folly/folly/portability/SysMman.h too large to diff
- folly/folly/portability/SysSyscall.h too large to diff
- folly/folly/portability/SysTypes.h too large to diff
- folly/folly/portability/Time.cpp too large to diff
- folly/folly/portability/Unistd.h too large to diff
- folly/folly/portability/openat2.c too large to diff
- folly/folly/python/AsyncioExecutor.h too large to diff
- folly/folly/python/GILAwareManualExecutor.h too large to diff
- folly/folly/python/ProactorExecutor.h too large to diff
- folly/folly/python/Weak.h too large to diff
- folly/folly/python/fibers.h too large to diff
- folly/folly/python/import.h too large to diff
- folly/folly/python/iobuf_ext.h too large to diff
- folly/folly/python/request_context.h too large to diff
- folly/folly/random/hash.h too large to diff
- folly/folly/random/seed_seq.h too large to diff
- folly/folly/random/xoshiro256pp.h too large to diff
- folly/folly/result/coded_rich_error.h too large to diff
- folly/folly/result/coro.h too large to diff
- folly/folly/result/detail/immortal_exception_storage.h too large to diff
- folly/folly/result/detail/ptr_immortal_exception_storage.h too large to diff
- folly/folly/result/detail/result_or_unwind.h too large to diff
- folly/folly/result/detail/result_promise.h too large to diff
- folly/folly/result/detail/rich_error_common.h too large to diff
- folly/folly/result/detail/rich_exception_ptr_storage.cpp too large to diff
- folly/folly/result/detail/rich_exception_ptr_storage.h too large to diff
- folly/folly/result/epitaph.cpp too large to diff
- folly/folly/result/epitaph.h too large to diff
- folly/folly/result/errc_rich_error.cpp too large to diff
- folly/folly/result/errc_rich_error.h too large to diff
- folly/folly/result/gtest_helpers.h too large to diff
- folly/folly/result/immortal_rich_error.h too large to diff
- folly/folly/result/nestable_coded_rich_error.h too large to diff
- folly/folly/result/or_unwind_epitaph.h too large to diff
- folly/folly/result/result.cpp too large to diff
- folly/folly/result/result.h too large to diff
- folly/folly/result/rich_error.h too large to diff
- folly/folly/result/rich_error_base.cpp too large to diff
- folly/folly/result/rich_error_base.h too large to diff
- folly/folly/result/rich_error_code.cpp too large to diff
- folly/folly/result/rich_error_code.h too large to diff
- folly/folly/result/rich_error_fwd.h too large to diff
- folly/folly/result/rich_exception_ptr.cpp too large to diff
- folly/folly/result/rich_exception_ptr.h too large to diff
- folly/folly/result/rich_msg.h too large to diff
- folly/folly/result/try.h too large to diff
- folly/folly/result/value_only_result.h too large to diff
- folly/folly/result/value_only_result_coro.h too large to diff
- folly/folly/settings/Observer.h too large to diff
- folly/folly/settings/Settings.cpp too large to diff
- folly/folly/settings/Settings.h too large to diff
- folly/folly/settings/Types.h too large to diff
- folly/folly/settings/detail/SettingsImpl.h too large to diff
- folly/folly/small_vector.h too large to diff
- folly/folly/sorted_vector_types.h too large to diff
- folly/folly/ssl/OpenSSLCertUtils.cpp too large to diff
- folly/folly/ssl/OpenSSLCertUtils.h too large to diff
- folly/folly/ssl/OpenSSLHash.cpp too large to diff
- folly/folly/ssl/OpenSSLHash.h too large to diff
- folly/folly/ssl/OpenSSLKeyUtils.cpp too large to diff
- folly/folly/ssl/OpenSSLKeyUtils.h too large to diff
- folly/folly/stats/BucketedTimeSeries-inl.h too large to diff
- folly/folly/stats/BucketedTimeSeries.h too large to diff
- folly/folly/stats/DigestBuilder-inl.h too large to diff
- folly/folly/stats/Histogram.h too large to diff
- folly/folly/stats/MultiLevelTimeSeries-inl.h too large to diff
- folly/folly/stats/MultiLevelTimeSeries.h too large to diff
- folly/folly/stats/QuantileEstimator.cpp too large to diff
- folly/folly/stats/TimeseriesHistogram.h too large to diff
- folly/folly/stats/detail/Bucket.h too large to diff
- folly/folly/synchronization/AsymmetricThreadFence.h too large to diff
- folly/folly/synchronization/AtomicNotification-inl.h too large to diff
- folly/folly/synchronization/AtomicUtil.h too large to diff
- folly/folly/synchronization/Baton.h too large to diff
- folly/folly/synchronization/CallOnce.h too large to diff
- folly/folly/synchronization/DistributedMutex-inl.h too large to diff
- folly/folly/synchronization/DistributedMutex.h too large to diff
- folly/folly/synchronization/FlatCombining.h too large to diff
- folly/folly/synchronization/Hazptr-fwd.h too large to diff
- folly/folly/synchronization/Hazptr.h too large to diff
- folly/folly/synchronization/HazptrDomain.cpp too large to diff
- folly/folly/synchronization/HazptrDomain.h too large to diff
- folly/folly/synchronization/HazptrHolder.h too large to diff
- folly/folly/synchronization/HazptrObj.h too large to diff
- folly/folly/synchronization/HazptrRec.h too large to diff
- folly/folly/synchronization/HazptrThreadPoolExecutor.cpp too large to diff
- folly/folly/synchronization/Lock.h too large to diff
- folly/folly/synchronization/MicroSpinLock.h too large to diff
- folly/folly/synchronization/RWSpinLock.h too large to diff
- folly/folly/synchronization/Rcu.h too large to diff
- folly/folly/synchronization/RelaxedAtomic.h too large to diff
- folly/folly/synchronization/SaturatingSemaphore.h too large to diff
- folly/folly/synchronization/StripedThrottledLifoSem.cpp too large to diff
- folly/folly/synchronization/StripedThrottledLifoSem.h too large to diff
- folly/folly/synchronization/ThrottledLifoSem.h too large to diff
- folly/folly/synchronization/WaitOptions.cpp too large to diff
- folly/folly/synchronization/detail/AtomicUtils.h too large to diff
- folly/folly/synchronization/detail/Hardware.cpp too large to diff
- folly/folly/synchronization/detail/Hardware.h too large to diff
- folly/folly/synchronization/detail/Sleeper.cpp too large to diff
- folly/folly/system/EnvUtil.cpp too large to diff
- folly/folly/system/EnvUtil.h too large to diff
- folly/folly/system/HardwareConcurrency.cpp too large to diff
- folly/folly/system/HardwareConcurrency.h too large to diff
- folly/folly/system/MemoryMapping.cpp too large to diff
- folly/folly/system/MemoryMapping.h too large to diff
- folly/folly/system/arch/x86.h too large to diff
- folly/folly/test/DeterministicSchedule.h too large to diff
- folly/folly/testing/TestUtil.cpp too large to diff
- folly/folly/testing/TestUtil.h too large to diff
- folly/folly/tracing/AsyncStack.cpp too large to diff
- folly/folly/tracing/AsyncStack.h too large to diff
@@ -1,6 +1,6 @@ cabal-version: 3.4 name: folly-clib-version: 20250713.1537+version: 20260203.1245 synopsis: The folly C++ library from Meta author: Simon Marlow maintainer: marlowsd@gmail.com@@ -31,7 +31,7 @@ location: https://github.com/facebook/folly.git common fb-cpp- cxx-options: -std=c++17+ cxx-options: -std=c++20 if !flag(clang) cxx-options: -fcoroutines if arch(x86_64)@@ -64,6 +64,8 @@ -- The contents of cxx-sources and install-includes get spliced -- in by running 'make setup-folly' cxx-sources:+ folly/folly/Benchmark.cpp + folly/folly/BenchmarkUtil.cpp folly/folly/CancellationToken.cpp folly/folly/ClockGettimeWrappers.cpp folly/folly/Conv.cpp @@ -75,39 +77,40 @@ folly/folly/FileUtil.cpp folly/folly/Fingerprint.cpp folly/folly/FmtUtility.cpp - folly/folly/FollyMemcpy.cpp - folly/folly/FollyMemset.cpp folly/folly/Format.cpp folly/folly/GroupVarint.cpp + folly/folly/FollyMemcpy.cpp + folly/folly/FollyMemcpy.cpp + folly/folly/memcpy.S + folly/folly/FollyMemset.cpp + folly/folly/FollyMemset.cpp + folly/folly/MicroLock.cpp folly/folly/IPAddress.cpp folly/folly/IPAddressV4.cpp folly/folly/IPAddressV6.cpp folly/folly/MacAddress.cpp - folly/folly/MicroLock.cpp + folly/folly/SocketAddress.cpp folly/folly/Random.cpp folly/folly/ScopeGuard.cpp folly/folly/SharedMutex.cpp folly/folly/Singleton.cpp folly/folly/SingletonThreadLocal.cpp - folly/folly/SocketAddress.cpp folly/folly/String.cpp folly/folly/Subprocess.cpp folly/folly/TimeoutQueue.cpp folly/folly/Try.cpp folly/folly/Unicode.cpp folly/folly/Uri.cpp - folly/folly/memcpy_select_aarch64.cpp - folly/folly/memset_select_aarch64.cpp folly/folly/algorithm/simd/Contains.cpp folly/folly/channels/MaxConcurrentRateLimiter.cpp folly/folly/cli/NestedCommandLineApp.cpp folly/folly/cli/ProgramOptions.cpp folly/folly/compression/Compression.cpp + folly/folly/compression/Zlib.cpp + folly/folly/compression/Zstd.cpp folly/folly/compression/CompressionContextPoolSingletons.cpp folly/folly/compression/QuotientMultiSet.cpp folly/folly/compression/Select64.cpp - folly/folly/compression/Zlib.cpp - folly/folly/compression/Zstd.cpp folly/folly/concurrency/CacheLocality.cpp folly/folly/concurrency/DeadlockDetector.cpp folly/folly/concurrency/ProcessLocalUniqueId.cpp @@ -115,16 +118,21 @@ folly/folly/container/RegexMatchCache.cpp folly/folly/container/detail/F14Table.cpp folly/folly/coro/Baton.cpp + folly/folly/coro/detail/Malloc.cpp folly/folly/coro/Mutex.cpp folly/folly/coro/SerialQueueRunner.cpp folly/folly/coro/SharedMutex.cpp - folly/folly/coro/detail/Malloc.cpp folly/folly/crypto/Blake2xb.cpp folly/folly/crypto/LtHash.cpp + folly/folly/crypto/LtHash.cpp + folly/folly/crypto/LtHash.cpp + folly/folly/crypto/detail/MathOperation_Simple.cpp + folly/folly/crypto/detail/MathOperation_SSE2.cpp folly/folly/crypto/detail/MathOperation_AVX2.cpp folly/folly/crypto/detail/MathOperation_SSE2.cpp - folly/folly/crypto/detail/MathOperation_Simple.cpp + folly/folly/crypto/detail/MathOperation_AVX2.cpp folly/folly/debugging/exception_tracer/ExceptionCounterLib.cpp + folly/folly/debugging/exception_tracer/ExceptionCounterLibStaticRegistration.cpp folly/folly/debugging/exception_tracer/ExceptionStackTraceLib.cpp folly/folly/debugging/exception_tracer/ExceptionTracer.cpp folly/folly/debugging/exception_tracer/ExceptionTracerLib.cpp @@ -157,46 +165,45 @@ folly/folly/detail/RangeSse42.cpp folly/folly/detail/SimpleSimdStringUtils.cpp folly/folly/detail/SocketFastOpen.cpp - folly/folly/detail/SplitStringSimd.cpp folly/folly/detail/Sse.cpp folly/folly/detail/StaticSingletonManager.cpp folly/folly/detail/ThreadLocalDetail.cpp + folly/folly/detail/thread_local_globals.cpp folly/folly/detail/TrapOnAvx512.cpp folly/folly/detail/UniqueInstance.cpp folly/folly/detail/base64_detail/Base64Api.cpp - folly/folly/detail/base64_detail/Base64SWAR.cpp folly/folly/detail/base64_detail/Base64_SSE4_2.cpp - folly/folly/detail/thread_local_globals.cpp - folly/folly/executors/CPUThreadPoolExecutor.cpp + folly/folly/detail/base64_detail/Base64SWAR.cpp folly/folly/executors/Codel.cpp + folly/folly/executors/CPUThreadPoolExecutor.cpp folly/folly/executors/EDFThreadPoolExecutor.cpp folly/folly/executors/ExecutionObserver.cpp folly/folly/executors/ExecutorWithPriority.cpp folly/folly/executors/FunctionScheduler.cpp folly/folly/executors/GlobalExecutor.cpp folly/folly/executors/GlobalThreadPoolList.cpp + folly/folly/executors/InlineExecutor.cpp folly/folly/executors/IOThreadPoolDeadlockDetectorObserver.cpp folly/folly/executors/IOThreadPoolExecutor.cpp - folly/folly/executors/InlineExecutor.cpp folly/folly/executors/ManualExecutor.cpp folly/folly/executors/QueueObserver.cpp folly/folly/executors/QueuedImmediateExecutor.cpp folly/folly/executors/SoftRealTimeExecutor.cpp folly/folly/executors/StrandExecutor.cpp + folly/folly/executors/StripedEDFThreadPoolExecutor.cpp folly/folly/executors/ThreadPoolExecutor.cpp folly/folly/executors/ThreadedExecutor.cpp folly/folly/executors/ThreadedRepeatingFunctionRunner.cpp folly/folly/executors/TimedDrivableExecutor.cpp folly/folly/executors/TimekeeperScheduledExecutor.cpp folly/folly/executors/thread_factory/PriorityThreadFactory.cpp - folly/folly/ext/buck2/test_ext.cpp folly/folly/ext/test_ext.cpp folly/folly/external/farmhash/farmhash.cpp folly/folly/external/fast-crc32/avx512_crc32c_v8s3x4.cpp + folly/folly/external/fast-crc32/sse_crc32c_v8s3x3.cpp folly/folly/external/fast-crc32/neon_crc32c_v3s4x2e_v2.cpp - folly/folly/external/fast-crc32/neon_eor3_crc32_v9s3x2e_s3.cpp folly/folly/external/fast-crc32/neon_eor3_crc32c_v8s2x4_s3.cpp - folly/folly/external/fast-crc32/sse_crc32c_v8s3x3.cpp + folly/folly/external/fast-crc32/neon_eor3_crc32_v9s3x2e_s3.cpp folly/folly/external/nvidia/detail/RangeSve2.cpp folly/folly/external/nvidia/hash/Checksum.cpp folly/folly/external/nvidia/hash/detail/Crc32cDetail.cpp @@ -213,23 +220,24 @@ folly/folly/futures/Barrier.cpp folly/folly/futures/Future.cpp folly/folly/futures/HeapTimekeeper.cpp - folly/folly/futures/ManualTimekeeper.cpp folly/folly/futures/Promise.cpp - folly/folly/futures/SharedPromise.cpp folly/folly/futures/ThreadWheelTimekeeper.cpp + folly/folly/futures/ManualTimekeeper.cpp + folly/folly/futures/SharedPromise.cpp folly/folly/futures/detail/Core.cpp folly/folly/hash/Checksum.cpp folly/folly/hash/SpookyHashV1.cpp folly/folly/hash/SpookyHashV2.cpp + folly/folly/hash/UniqueHashKey.cpp folly/folly/hash/detail/ChecksumDetail.cpp folly/folly/hash/detail/Crc32CombineDetail.cpp folly/folly/hash/detail/Crc32cDetail.cpp folly/folly/init/Init.cpp folly/folly/init/Phase.cpp - folly/folly/io/Cursor.cpp folly/folly/io/FsUtil.cpp folly/folly/io/GlobalShutdownSocketSet.cpp folly/folly/io/HugePages.cpp + folly/folly/io/Cursor.cpp folly/folly/io/IOBuf.cpp folly/folly/io/IOBufIovecBuilder.cpp folly/folly/io/IOBufQueue.cpp @@ -237,98 +245,104 @@ folly/folly/io/ShutdownSocketSet.cpp folly/folly/io/SocketOptionMap.cpp folly/folly/io/SocketOptionValue.cpp + folly/folly/io/async/AsyncTimeout.cpp + folly/folly/io/async/EventBase.cpp + folly/folly/io/async/EventBaseBackendBase.cpp + folly/folly/io/async/EventBaseLocal.cpp + folly/folly/io/async/EventHandler.cpp + folly/folly/io/async/HHWheelTimer.cpp + folly/folly/io/async/TimeoutManager.cpp + folly/folly/io/async/VirtualEventBase.cpp folly/folly/io/async/AsyncBase.cpp folly/folly/io/async/AsyncIO.cpp folly/folly/io/async/AsyncIoUringSocket.cpp + folly/folly/io/async/AsyncIoUringSocketFactory.cpp folly/folly/io/async/AsyncPipe.cpp - folly/folly/io/async/AsyncSSLSocket.cpp - folly/folly/io/async/AsyncServerSocket.cpp folly/folly/io/async/AsyncSignalHandler.cpp folly/folly/io/async/AsyncSocket.cpp folly/folly/io/async/AsyncSocketException.cpp folly/folly/io/async/AsyncSocketTransport.cpp - folly/folly/io/async/AsyncTimeout.cpp + folly/folly/io/async/AsyncSSLSocket.cpp folly/folly/io/async/AsyncUDPSocket.cpp folly/folly/io/async/DelayedDestruction.cpp folly/folly/io/async/EpollBackend.cpp - folly/folly/io/async/EventBase.cpp - folly/folly/io/async/EventBaseBackendBase.cpp - folly/folly/io/async/EventBaseLocal.cpp folly/folly/io/async/EventBaseManager.cpp folly/folly/io/async/EventBasePoller.cpp folly/folly/io/async/EventBaseThread.cpp - folly/folly/io/async/EventHandler.cpp - folly/folly/io/async/HHWheelTimer.cpp folly/folly/io/async/IoUring.cpp folly/folly/io/async/IoUringBackend.cpp + folly/folly/io/async/IoUringConnect.cpp folly/folly/io/async/IoUringEvent.cpp folly/folly/io/async/IoUringEventBaseLocal.cpp folly/folly/io/async/IoUringProvidedBufferRing.cpp + folly/folly/io/async/IoUringRecv.cpp + folly/folly/io/async/IoUringSend.cpp folly/folly/io/async/IoUringZeroCopyBufferPool.cpp folly/folly/io/async/MuxIOThreadPoolExecutor.cpp folly/folly/io/async/PasswordInFile.cpp folly/folly/io/async/Request.cpp - folly/folly/io/async/SSLContext.cpp - folly/folly/io/async/SSLOptions.cpp - folly/folly/io/async/STTimerFDTimeoutManager.cpp folly/folly/io/async/ScopedEventBaseThread.cpp + folly/folly/io/async/AsyncServerSocket.cpp folly/folly/io/async/SimpleAsyncIO.cpp + folly/folly/io/async/SSLContext.cpp + folly/folly/io/async/SSLOptions.cpp folly/folly/io/async/TerminateCancellationToken.cpp - folly/folly/io/async/TimeoutManager.cpp + folly/folly/io/async/STTimerFDTimeoutManager.cpp folly/folly/io/async/TimerFD.cpp folly/folly/io/async/TimerFDTimeoutManager.cpp - folly/folly/io/async/VirtualEventBase.cpp folly/folly/io/async/fdsock/AsyncFdSocket.cpp folly/folly/io/async/fdsock/SocketFds.cpp folly/folly/io/async/ssl/OpenSSLUtils.cpp folly/folly/io/async/ssl/SSLErrors.cpp folly/folly/io/coro/ServerSocket.cpp folly/folly/io/coro/Transport.cpp + folly/folly/json/dynamic.cpp + folly/folly/json/json.cpp folly/folly/json/DynamicParser.cpp + folly/folly/json/json_patch.cpp + folly/folly/json/json_pointer.cpp folly/folly/json/JSONSchema.cpp folly/folly/json/JsonTestUtil.cpp folly/folly/json/bser/Dump.cpp folly/folly/json/bser/Load.cpp - folly/folly/json/dynamic.cpp - folly/folly/json/json.cpp - folly/folly/json/json_patch.cpp - folly/folly/json/json_pointer.cpp folly/folly/lang/CString.cpp folly/folly/lang/Exception.cpp folly/folly/lang/SafeAssert.cpp folly/folly/lang/ToAscii.cpp folly/folly/lang/UncaughtExceptions.cpp + folly/folly/logging/FileHandlerFactory.cpp + folly/folly/logging/BridgeFromGoogleLogging.cpp + folly/folly/logging/Init.cpp + folly/folly/logging/LogConfigParser.cpp + folly/folly/logging/InitWeak.cpp + folly/folly/logging/LogHandlerConfig.cpp + folly/folly/logging/LogLevel.cpp + folly/folly/logging/LogName.cpp folly/folly/logging/AsyncFileWriter.cpp folly/folly/logging/AsyncLogWriter.cpp - folly/folly/logging/BridgeFromGoogleLogging.cpp folly/folly/logging/CustomLogFormatter.cpp - folly/folly/logging/FileHandlerFactory.cpp folly/folly/logging/FileWriterFactory.cpp folly/folly/logging/GlogStyleFormatter.cpp folly/folly/logging/ImmediateFileWriter.cpp - folly/folly/logging/Init.cpp - folly/folly/logging/InitWeak.cpp folly/folly/logging/LogCategory.cpp folly/folly/logging/LogCategoryConfig.cpp folly/folly/logging/LogConfig.cpp - folly/folly/logging/LogConfigParser.cpp - folly/folly/logging/LogHandlerConfig.cpp - folly/folly/logging/LogLevel.cpp folly/folly/logging/LogMessage.cpp - folly/folly/logging/LogName.cpp folly/folly/logging/LogStream.cpp folly/folly/logging/LogStreamProcessor.cpp folly/folly/logging/Logger.cpp folly/folly/logging/LoggerDB.cpp folly/folly/logging/ObjectToString.cpp - folly/folly/logging/RateLimiter.cpp folly/folly/logging/StandardLogHandler.cpp folly/folly/logging/StandardLogHandlerFactory.cpp folly/folly/logging/StreamHandlerFactory.cpp folly/folly/logging/xlog.cpp + folly/folly/logging/RateLimiter.cpp + folly/folly/memory/IoUringArena.cpp folly/folly/memory/JemallocHugePageAllocator.cpp folly/folly/memory/JemallocNodumpAllocator.cpp folly/folly/memory/MallctlHelper.cpp + folly/folly/memory/Malloc.cpp folly/folly/memory/ReentrantAllocator.cpp folly/folly/memory/SanitizeAddress.cpp folly/folly/memory/SanitizeLeak.cpp @@ -348,6 +362,7 @@ folly/folly/portability/GFlags.cpp folly/folly/portability/Libgen.cpp folly/folly/portability/Malloc.cpp + folly/folly/portability/openat2.c folly/folly/portability/OpenSSL.cpp folly/folly/portability/PThread.cpp folly/folly/portability/Sched.cpp @@ -364,7 +379,13 @@ folly/folly/portability/SysUio.cpp folly/folly/portability/Time.cpp folly/folly/portability/Unistd.cpp + folly/folly/result/epitaph.cpp + folly/folly/result/errc_rich_error.cpp folly/folly/result/result.cpp + folly/folly/result/rich_error_base.cpp + folly/folly/result/rich_exception_ptr.cpp + folly/folly/result/rich_error_code.cpp + folly/folly/result/detail/rich_exception_ptr_storage.cpp folly/folly/settings/CommandLineParser.cpp folly/folly/settings/Immutables.cpp folly/folly/settings/Settings.cpp @@ -388,8 +409,7 @@ folly/folly/synchronization/ParkingLot.cpp folly/folly/synchronization/Rcu.cpp folly/folly/synchronization/SanitizeThread.cpp - folly/folly/synchronization/WaitOptions.cpp - folly/folly/synchronization/detail/Hardware.cpp + folly/folly/synchronization/StripedThrottledLifoSem.cpp folly/folly/synchronization/detail/Sleeper.cpp folly/folly/system/AtFork.cpp folly/folly/system/EnvUtil.cpp @@ -401,9 +421,6 @@ folly/folly/system/ThreadName.cpp folly/folly/testing/TestUtil.cpp folly/folly/tracing/AsyncStack.cpp - folly/folly/io/async/test/ScopedBoundPort.cpp - folly/folly/io/async/test/SocketPair.cpp - folly/folly/io/async/test/TimeUtil.cpp install-includes: folly/AtomicHashArray-inl.h @@ -621,6 +638,7 @@ folly/container/Access.h folly/container/Array.h folly/container/BitIterator.h + folly/container/CollectionUtil.h folly/container/Enumerate.h folly/container/EvictingCacheMap.h folly/container/F14Map-fwd.h @@ -655,6 +673,7 @@ folly/container/detail/Util.h folly/container/detail/tape_detail.h folly/container/heap_vector_types.h + folly/container/irange.h folly/container/range_traits.h folly/container/small_vector.h folly/container/sorted_vector_types.h @@ -669,8 +688,7 @@ folly/coro/AsyncStack.h folly/coro/AutoCleanup-fwd.h folly/coro/AutoCleanup.h - folly/coro/AwaitImmediately.h - folly/coro/AwaitResult.h + folly/coro/BasePromise.h folly/coro/Baton.h folly/coro/BlockingWait.h folly/coro/BoundedQueue.h @@ -682,6 +700,7 @@ folly/coro/Coroutine.h folly/coro/CurrentExecutor.h folly/coro/DetachOnCancel.h + folly/coro/Error.h folly/coro/Filter-inl.h folly/coro/Filter.h folly/coro/FutureUtil.h @@ -692,9 +711,8 @@ folly/coro/Merge-inl.h folly/coro/Merge.h folly/coro/Mutex.h - folly/coro/Noexcept.h + folly/coro/Nothrow.h folly/coro/Promise.h - folly/coro/Ready.h folly/coro/Result.h folly/coro/Retry.h folly/coro/RustAdaptors.h @@ -716,6 +734,8 @@ folly/coro/Transform-inl.h folly/coro/Transform.h folly/coro/UnboundedQueue.h + folly/coro/ValueOrError.h + folly/coro/ValueOrFatal.h folly/coro/ViaIfAsync.h folly/coro/WithAsyncStack.h folly/coro/WithCancellation.h @@ -730,12 +750,12 @@ folly/coro/detail/Traits.h folly/coro/safe/AsyncClosure-fwd.h folly/coro/safe/AsyncClosure.h + folly/coro/safe/BindCaptures.h folly/coro/safe/Captures.h folly/coro/safe/NowTask.h - folly/coro/safe/SafeAlias.h folly/coro/safe/SafeTask.h folly/coro/safe/detail/AsyncClosure.h - folly/coro/safe/detail/AsyncClosureBindings.h + folly/coro/safe/detail/BindAsyncClosure.h folly/coro/safe/detail/DefineMovableDeepConstLrefCopyable.h folly/crypto/Blake2xb.h folly/crypto/LtHash-inl.h @@ -839,6 +859,7 @@ folly/executors/SerializedExecutor.h folly/executors/SoftRealTimeExecutor.h folly/executors/StrandExecutor.h + folly/executors/StripedEDFThreadPoolExecutor.h folly/executors/ThreadPoolExecutor.h folly/executors/ThreadedExecutor.h folly/executors/ThreadedRepeatingFunctionRunner.h @@ -849,6 +870,7 @@ folly/executors/task_queue/LifoSemMPMCQueue.h folly/executors/task_queue/PriorityLifoSemMPMCQueue.h folly/executors/task_queue/PriorityUnboundedBlockingQueue.h + folly/executors/task_queue/StripedPriorityUnboundedBlockingQueue.h folly/executors/task_queue/UnboundedBlockingQueue.h folly/executors/thread_factory/InitThreadFactory.h folly/executors/thread_factory/NamedThreadFactory.h @@ -857,7 +879,6 @@ folly/experimental/EventCount.h folly/experimental/FlatCombiningPriorityQueue.h folly/experimental/FunctionScheduler.h - folly/experimental/TestUtil.h folly/experimental/ThreadedRepeatingFunctionRunner.h folly/experimental/channels/Channel-fwd.h folly/experimental/channels/Channel-inl.h @@ -1112,11 +1133,15 @@ folly/gen/String.h folly/hash/Checksum.h folly/hash/FarmHash.h + folly/hash/FnvHash.h folly/hash/Hash.h + folly/hash/HsiehHash.h folly/hash/MurmurHash.h folly/hash/SpookyHashV1.h folly/hash/SpookyHashV2.h + folly/hash/UniqueHashKey.h folly/hash/detail/ChecksumDetail.h + folly/hash/detail/RandomSeed.h folly/hash/rapidhash.h folly/hash/traits.h folly/init/Init.h @@ -1176,9 +1201,12 @@ folly/io/async/IoUring.h folly/io/async/IoUringBackend.h folly/io/async/IoUringBase.h + folly/io/async/IoUringConnect.h folly/io/async/IoUringEvent.h folly/io/async/IoUringEventBaseLocal.h folly/io/async/IoUringProvidedBufferRing.h + folly/io/async/IoUringRecv.h + folly/io/async/IoUringSend.h folly/io/async/IoUringZeroCopyBufferPool.h folly/io/async/Liburing.h folly/io/async/MuxIOThreadPoolExecutor.h @@ -1227,7 +1255,6 @@ folly/lang/Aligned.h folly/lang/Assume.h folly/lang/Badge.h - folly/lang/Bindings.h folly/lang/Bits.h folly/lang/BitsClass.h folly/lang/Builtin.h @@ -1241,21 +1268,30 @@ folly/lang/Hint-inl.h folly/lang/Hint.h folly/lang/Keep.h + folly/lang/MustUseImmediately.h folly/lang/New.h folly/lang/Ordering.h folly/lang/Pretty.h folly/lang/PropagateConst.h folly/lang/RValueReferenceWrapper.h folly/lang/SafeAlias-fwd.h + folly/lang/SafeAlias.h folly/lang/SafeAssert.h + folly/lang/SafeClosure.h folly/lang/StaticConst.h + folly/lang/Strong.h folly/lang/Switch.h folly/lang/Thunk.h folly/lang/ToAscii.h folly/lang/TypeInfo.h folly/lang/UncaughtExceptions.h folly/lang/VectorTraits.h - folly/lang/named/Bindings.h + folly/lang/bind/AsArgument.h + folly/lang/bind/Bind.h + folly/lang/bind/Named.h + folly/lang/bind/NamedToStorage.h + folly/lang/bind/ToStorage.h + folly/lang/cstring_view.h folly/logging/AsyncFileWriter.h folly/logging/AsyncLogWriter.h folly/logging/AutoTimer.h @@ -1290,6 +1326,7 @@ folly/logging/xlog.h folly/memory/Arena-inl.h folly/memory/Arena.h + folly/memory/IoUringArena.h folly/memory/JemallocHugePageAllocator.h folly/memory/JemallocNodumpAllocator.h folly/memory/MallctlHelper.h @@ -1371,18 +1408,46 @@ folly/portability/Windows.h folly/portability/openat2.h folly/python/AsyncioExecutor.h + folly/python/GILAwareManualExecutor.h + folly/python/ProactorExecutor.h folly/python/Weak.h folly/python/async_generator.h folly/python/coro.h folly/python/error.h folly/python/executor.h + folly/python/fibers.h folly/python/futures.h folly/python/import.h folly/python/iobuf.h + folly/python/iobuf_ext.h + folly/python/request_context.h + folly/random/hash.h + folly/random/seed_seq.h folly/random/xoshiro256pp.h + folly/result/coded_rich_error.h + folly/result/coro.h + folly/result/detail/immortal_exception_storage.h + folly/result/detail/ptr_immortal_exception_storage.h + folly/result/detail/result_or_unwind.h + folly/result/detail/result_promise.h + folly/result/detail/rich_error_common.h + folly/result/detail/rich_exception_ptr_storage.h + folly/result/epitaph.h + folly/result/errc_rich_error.h folly/result/gtest_helpers.h + folly/result/immortal_rich_error.h + folly/result/nestable_coded_rich_error.h + folly/result/or_unwind_epitaph.h folly/result/result.h + folly/result/rich_error.h + folly/result/rich_error_base.h + folly/result/rich_error_code.h + folly/result/rich_error_fwd.h + folly/result/rich_exception_ptr.h + folly/result/rich_msg.h folly/result/try.h + folly/result/value_only_result.h + folly/result/value_only_result_coro.h folly/settings/CommandLineParser.h folly/settings/Immutables.h folly/settings/Observer.h @@ -1458,10 +1523,10 @@ folly/synchronization/SanitizeThread.h folly/synchronization/SaturatingSemaphore.h folly/synchronization/SmallLocks.h + folly/synchronization/StripedThrottledLifoSem.h folly/synchronization/ThrottledLifoSem.h folly/synchronization/WaitOptions.h folly/synchronization/detail/AtomicUtils.h - folly/synchronization/detail/Hardware.h folly/synchronization/detail/HazptrUtils.h folly/synchronization/detail/InlineFunctionRef.h folly/synchronization/detail/Sleeper.h @@ -1481,6 +1546,7 @@ folly/system/Shell.h folly/system/ThreadId.h folly/system/ThreadName.h + folly/system/arch/x86.h folly/testing/TestUtil.h folly/tracing/AsyncStack-inl.h folly/tracing/AsyncStack.h
@@ -56,20 +56,20 @@ #define FOLLY_HAVE_WEAK_SYMBOLS 1 #define FOLLY_HAVE_LINUX_VDSO 1 #define FOLLY_HAVE_MALLOC_USABLE_SIZE 1-/* #undef FOLLY_HAVE_INT128_T */+#define FOLLY_HAVE_INT128_T 1 #define FOLLY_HAVE_WCHAR_SUPPORT 1 #define FOLLY_HAVE_EXTRANDOM_SFMT19937 1 #define HAVE_VSNPRINTF_ERRORS 1 #define FOLLY_HAVE_LIBUNWIND 1-/* #undef FOLLY_HAVE_DWARF */+#define FOLLY_HAVE_DWARF 0 #define FOLLY_HAVE_ELF 1 #define FOLLY_HAVE_SWAPCONTEXT 1 #define FOLLY_HAVE_BACKTRACE 1 #define FOLLY_USE_SYMBOLIZER 1 #define FOLLY_DEMANGLE_MAX_SYMBOL_SIZE 1024 -#define FOLLY_HAVE_SHADOW_LOCAL_WARNINGS 1+/* #undef FOLLY_HAVE_SHADOW_LOCAL_WARNINGS */ #define FOLLY_HAVE_LIBLZ4 1 #define FOLLY_HAVE_LIBLZMA 1
@@ -19,7 +19,7 @@ * core lock-free functionality, but is limited by the fact that it cannot * grow past its initialization size and is a little more awkward (no public * constructor, for example). If you're confident that you won't run out of- * space, don't mind the awkardness, and really need bare-metal performance,+ * space, don't mind the awkwardness, and really need bare-metal performance, * feel free to use AHA directly. * * Check out AtomicHashMap.h for more thorough documentation on perf and@@ -104,18 +104,18 @@ "keys, or a different container class."); public:- typedef KeyT key_type;- typedef ValueT mapped_type;- typedef HashFcn hasher;- typedef EqualFcn key_equal;- typedef KeyConvertFcn key_convert;- typedef std::pair<const KeyT, ValueT> value_type;- typedef std::size_t size_type;- typedef std::ptrdiff_t difference_type;- typedef value_type& reference;- typedef const value_type& const_reference;- typedef value_type* pointer;- typedef const value_type* const_pointer;+ using key_type = KeyT;+ using mapped_type = ValueT;+ using hasher = HashFcn;+ using key_equal = EqualFcn;+ using key_convert = KeyConvertFcn;+ using value_type = std::pair<const KeyT, ValueT>;+ using size_type = std::size_t;+ using difference_type = std::ptrdiff_t;+ using reference = value_type&;+ using const_reference = const value_type&;+ using pointer = value_type*;+ using const_pointer = const value_type*; const size_t capacity_; const size_t maxEntries_;@@ -126,12 +126,12 @@ template <class ContT, class IterVal> struct aha_iterator; - typedef aha_iterator<const AtomicHashArray, const value_type> const_iterator;- typedef aha_iterator<AtomicHashArray, value_type> iterator;+ using const_iterator = aha_iterator<const AtomicHashArray, const value_type>;+ using iterator = aha_iterator<AtomicHashArray, value_type>; // You really shouldn't need this if you use the SmartPtr provided by create, // but if you really want to do something crazy like stick the released- // pointer into a DescriminatedPtr or something, you'll need this to clean up+ // pointer into a DiscriminatedPtr or something, you'll need this to clean up // after yourself. static void destroy(AtomicHashArray*); @@ -143,7 +143,7 @@ }; public:- typedef std::unique_ptr<AtomicHashArray, Deleter> SmartPtr;+ using SmartPtr = std::unique_ptr<AtomicHashArray, Deleter>; /* * create --
@@ -40,7 +40,7 @@ T* next{nullptr}; }; -template <class T, AtomicIntrusiveLinkedListHook<T> T::*HookMember>+template <class T, AtomicIntrusiveLinkedListHook<T> T::* HookMember> class AtomicIntrusiveLinkedList { public: AtomicIntrusiveLinkedList() {}
@@ -122,13 +122,12 @@ /// /// MEMORY ALLOCATION ///-/// Underlying memory is allocated as a big anonymous mmap chunk, which-/// might be cheaper than calloc() and is certainly not more expensive-/// for large maps. If the SkipKeyValueDeletion template param is true-/// then deletion of the map consists of unmapping the backing memory,-/// which is much faster than destructing all of the keys and values.-/// Feel free to override if std::is_trivial_destructor isn't recognizing-/// the triviality of your destructors.+/// Underlying memory is allocated as a big anonymous chunk. If the+/// SkipKeyValueDeletion template param is true then deletion of the map+/// consists of deallocating the backing memory, which is much faster than+/// destructing all of the keys and values. Feel free to override if+/// std::is_trivial_destructor isn't recognizing the triviality of your+/// destructors. template < typename Key, typename Value,@@ -139,7 +138,7 @@ std::is_trivially_destructible<Value>::value), template <typename> class Atom = std::atomic, typename IndexType = uint32_t,- typename Allocator = folly::detail::MMapAlloc>+ typename Allocator = folly::detail::MallocAlloc> struct AtomicUnorderedInsertMap { using key_type = Key;@@ -222,8 +221,8 @@ numSlots_ = capacity; slotMask_ = folly::nextPowTwo(capacity * 4) - 1;- mmapRequested_ = sizeof(Slot) * capacity;- slots_ = reinterpret_cast<Slot*>(allocator_.allocate(mmapRequested_));+ allocRequested_ = sizeof(Slot) * capacity;+ slots_ = reinterpret_cast<Slot*>(allocator_.allocate(allocRequested_)); zeroFillSlots(); // mark the zero-th slot as in-use but not valid, since that happens // to be our nil value@@ -236,7 +235,7 @@ slots_[i].~Slot(); } }- allocator_.deallocate(reinterpret_cast<char*>(slots_), mmapRequested_);+ allocator_.deallocate(reinterpret_cast<char*>(slots_), allocRequested_); } /// Searches for the key, returning (iter,false) if it is found.@@ -404,7 +403,7 @@ // (by getting a zero-filled mmap chunk) and optionally destruction of // the slots - size_t mmapRequested_;+ size_t allocRequested_; size_t numSlots_; /// tricky, see keyToSlotIdx@@ -469,7 +468,7 @@ void zeroFillSlots() { using folly::detail::GivesZeroFilledMemory; if (!GivesZeroFilledMemory<Allocator>::value) {- memset(static_cast<void*>(slots_), 0, mmapRequested_);+ memset(static_cast<void*>(slots_), 0, allocRequested_); } } };@@ -487,7 +486,7 @@ (std::is_trivially_destructible<Key>::value && std::is_trivially_destructible<Value>::value), template <typename> class Atom = std::atomic,- typename Allocator = folly::detail::MMapAlloc>+ typename Allocator = folly::detail::MallocAlloc> using AtomicUnorderedInsertMap64 = AtomicUnorderedInsertMap< Key, Value,
@@ -0,0 +1,1036 @@+/*+ * Copyright (c) Meta Platforms, Inc. and affiliates.+ *+ * Licensed under the Apache License, Version 2.0 (the "License");+ * you may not use this file except in compliance with the License.+ * You may obtain a copy of the License at+ *+ * http://www.apache.org/licenses/LICENSE-2.0+ *+ * Unless required by applicable law or agreed to in writing, software+ * distributed under the License is distributed on an "AS IS" BASIS,+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.+ * See the License for the specific language governing permissions and+ * limitations under the License.+ */++#include <folly/Benchmark.h>++#include <algorithm>+#include <cmath>+#include <cstring>+#include <iostream>+#include <limits>+#include <map>+#include <memory>+#include <numeric>+#include <utility>+#include <vector>++#include <folly/FileUtil.h>+#include <folly/MapUtil.h>+#include <folly/Overload.h>+#include <folly/String.h>+#include <folly/detail/PerfScoped.h>+#include <folly/json/json.h>++// This needs to be at the end because some versions end up including+// Windows.h without defining NOMINMAX, which breaks uses+// of `std::numeric_limits<T>::max()`. We explicitly define NOMINMAX here+// explicitly instead.+#define NOMINMAX 1+#include <boost/regex.hpp>++using namespace std;++FOLLY_GFLAGS_DEFINE_bool(benchmark, false, "Run benchmarks.");++FOLLY_GFLAGS_DEFINE_bool(json, false, "Output in JSON format.");++FOLLY_GFLAGS_DEFINE_bool(+ bm_estimate_time,+ false,+ "Estimate running time by returning the geometric mean of latency values between p25 and p75.");++#if FOLLY_PERF_IS_SUPPORTED+FOLLY_GFLAGS_DEFINE_string(+ bm_perf_args,+ "",+ "Run selected benchmarks while attaching `perf` profiling tool."+ "Advantage over attaching perf externally is that this skips "+ "initialization. The first iteration of the benchmark is also "+ "skipped to allow for all statics to be set up. This requires perf "+ " to be available on the system. Example: --bm_perf_args=\"record -g\"");+#endif++FOLLY_GFLAGS_DEFINE_bool(+ bm_profile, false, "Run benchmarks with constant number of iterations");++FOLLY_GFLAGS_DEFINE_int64(+ bm_profile_iters, 1000, "Number of iterations for profiling");++FOLLY_GFLAGS_DEFINE_string(+ bm_relative_to,+ "",+ "Print benchmark results relative to an earlier dump (via --bm_json_verbose)");++FOLLY_GFLAGS_DEFINE_bool(+ bm_warm_up_iteration,+ false,+ "Run one iteration of the benchmarks before measuring. Always true if `bm_perf_args` is passed");++FOLLY_GFLAGS_DEFINE_string(+ bm_json_verbose,+ "",+ "File to write verbose JSON format (for BenchmarkCompare / --bm_relative_to). "+ "NOTE: this file is written regardless of options --json and --bm_relative_to.");++FOLLY_GFLAGS_DEFINE_string(+ bm_regex, "", "Only benchmarks whose names match this regex will be run.");++FOLLY_GFLAGS_DEFINE_string(+ bm_file_regex,+ "",+ "Only benchmarks whose filenames match this regex will be run.");++FOLLY_GFLAGS_DEFINE_int64(+ bm_min_usec,+ 100,+ "Minimum # of microseconds we'll accept for each benchmark.");++FOLLY_GFLAGS_DEFINE_int32(+ bm_min_iters, 1, "Minimum # of iterations we'll try for each benchmark.");++FOLLY_GFLAGS_DEFINE_int64(+ bm_max_iters,+ 1 << 30,+ "Maximum # of iterations we'll try for each benchmark.");++FOLLY_GFLAGS_DEFINE_int32(+ bm_max_secs, 1, "Maximum # of seconds we'll spend on each benchmark.");++FOLLY_GFLAGS_DEFINE_uint32(+ bm_result_width_chars, 76, "Width of results table in characters");++FOLLY_GFLAGS_DEFINE_uint32(+ bm_max_trials,+ 1000,+ "Maximum number of trials (iterations) executed for each benchmark.");++FOLLY_GFLAGS_DEFINE_bool(+ bm_list,+ false,+ "Print out list of all benchmark test names without running them.");++namespace folly {+namespace detail {++BenchmarkingState<std::chrono::high_resolution_clock>& globalBenchmarkState() {+ static detail::BenchmarkingState<std::chrono::high_resolution_clock> state;+ return state;+}++} // namespace detail++using BenchmarkFun = std::function<detail::TimeIterData(unsigned int)>;++#define FB_FOLLY_GLOBAL_BENCHMARK_BASELINE fbFollyGlobalBenchmarkBaseline+#define FB_FOLLY_GLOBAL_BENCHMARK_SUSPENDER_BASELINE \+ fbFollyGlobalBenchmarkSuspenderBaseline+#define FB_STRINGIZE_X2(x) FOLLY_PP_STRINGIZE(x)++constexpr const char kGlobalBenchmarkBaseline[] =+ FB_STRINGIZE_X2(FB_FOLLY_GLOBAL_BENCHMARK_BASELINE);++constexpr const char kGlobalBenchmarkSuspenderBaseline[] =+ FB_STRINGIZE_X2(FB_FOLLY_GLOBAL_BENCHMARK_SUSPENDER_BASELINE);++// Add the global baseline+BENCHMARK(FB_FOLLY_GLOBAL_BENCHMARK_BASELINE) {+#ifdef _MSC_VER+ _ReadWriteBarrier();+#else+ asm volatile("");+#endif+}++// Add the suspender overhead baseline+BENCHMARK(FB_FOLLY_GLOBAL_BENCHMARK_SUSPENDER_BASELINE) {+ BENCHMARK_SUSPEND {}+}++#undef FB_STRINGIZE_X2+#undef FB_FOLLY_GLOBAL_BENCHMARK_BASELINE++static std::pair<double, UserCounters> runBenchmarkGetNSPerIteration(+ const BenchmarkFun& fun, const double globalBaseline) {+ using std::chrono::duration_cast;+ using std::chrono::high_resolution_clock;+ using std::chrono::microseconds;+ using std::chrono::nanoseconds;+ using std::chrono::seconds;++ // They key here is accuracy; too low numbers means the accuracy was+ // coarse. We up the ante until we get to at least minNanoseconds+ // timings.+ static_assert(+ std::is_same<high_resolution_clock::duration, nanoseconds>::value,+ "High resolution clock must be nanosecond resolution.");+ // We choose a minimum minimum (sic) of 100,000 nanoseconds, but if+ // the clock resolution is worse than that, it will be larger. In+ // essence we're aiming at making the quantization noise 0.01%.+ static const auto minNanoseconds = std::max<nanoseconds>(+ nanoseconds(100000), microseconds(FLAGS_bm_min_usec));++ // We establish a total time budget as we don't want a measurement+ // to take too long. This will curtail the number of actual trials.+ const auto timeBudget = seconds(FLAGS_bm_max_secs);+ auto global = high_resolution_clock::now();++ std::vector<std::pair<double, UserCounters>> trialResults(+ FLAGS_bm_max_trials);+ size_t actualTrials = 0;++ // We do measurements in several trials (epochs) and take the minimum, to+ // account for jitter.+ for (; actualTrials < FLAGS_bm_max_trials; ++actualTrials) {+ const auto maxIters = uint32_t(FLAGS_bm_max_iters);+ for (auto n = uint32_t(FLAGS_bm_min_iters); n < maxIters; n *= 2) {+ detail::TimeIterData timeIterData = fun(static_cast<unsigned int>(n));+ if (timeIterData.duration < minNanoseconds) {+ continue;+ }+ // We got an accurate enough timing, done. But only save if+ // smaller than the current result.+ auto nsecs = duration_cast<nanoseconds>(timeIterData.duration);+ trialResults[actualTrials] = std::make_pair(+ max(0.0, double(nsecs.count()) / timeIterData.niter - globalBaseline),+ std::move(timeIterData.userCounters));+ // Done with the current trial, we got a meaningful timing.+ break;+ }+ auto now = high_resolution_clock::now();+ if (now - global >= timeBudget) {+ // No more time budget available.+ ++actualTrials;+ break;+ }+ }++ // Current state of the art: get the minimum. After some+ // experimentation, it seems taking the minimum is the best.+ auto iter = min_element(+ trialResults.begin(),+ trialResults.begin() + actualTrials,+ [](const auto& a, const auto& b) { return a.first < b.first; });++ // If the benchmark was basically drowned in baseline noise, it's+ // possible it became negative.+ return std::make_pair(max(0.0, iter->first), iter->second);+}++static std::pair<double, UserCounters> runBenchmarkGetNSPerIterationEstimate(+ const BenchmarkFun& fun, const double globalBaseline) {+ using std::chrono::duration_cast;+ using std::chrono::high_resolution_clock;+ using std::chrono::microseconds;+ using std::chrono::nanoseconds;+ using std::chrono::seconds;+ using TrialResultType = std::pair<double, UserCounters>;++ // They key here is accuracy; too low numbers means the accuracy was+ // coarse. We up the ante until we get to at least minNanoseconds+ // timings.+ static_assert(+ std::is_same<high_resolution_clock::duration, nanoseconds>::value,+ "High resolution clock must be nanosecond resolution.");++ // Estimate single iteration running time for 1 sec+ double estPerIter = 0.0; // Estimated nanosec per iteration+ auto estStart = high_resolution_clock::now();+ const auto estBudget = seconds(1);+ for (auto n = 1; n < 1000; n *= 2) {+ detail::TimeIterData timeIterData = fun(static_cast<unsigned int>(n));+ auto now = high_resolution_clock::now();+ auto nsecs = duration_cast<nanoseconds>(timeIterData.duration);+ estPerIter = double(nsecs.count() - globalBaseline) / n;+ if (now - estStart > estBudget) {+ break;+ }+ }+ // Can't estimate running time, so make it a baseline+ if (estPerIter <= 0.0) {+ estPerIter = globalBaseline;+ }++ // We do measurements in several trials (epochs) to account for jitter.+ size_t actualTrials = 0;+ const unsigned int estimateCount = to_integral(max(1.0, 5e+7 / estPerIter));+ std::vector<TrialResultType> trialResults(FLAGS_bm_max_trials);+ const auto maxRunTime = seconds(max(5, FLAGS_bm_max_secs));+ auto globalStart = high_resolution_clock::now();++ // Run benchmark up to trial times with at least 0.5 sec each+ // Or until we run out of allowed time (max(5, FLAGS_bm_max_secs))+ for (size_t tryId = 0; tryId < FLAGS_bm_max_trials; tryId++) {+ detail::TimeIterData timeIterData = fun(estimateCount);+ auto nsecs = duration_cast<nanoseconds>(timeIterData.duration);++ if (nsecs.count() > globalBaseline) {+ auto nsecIter =+ double(nsecs.count() - globalBaseline) / timeIterData.niter;+ trialResults[actualTrials++] =+ std::make_pair(nsecIter, std::move(timeIterData.userCounters));+ }+ // Check if we are out of time quota+ auto now = high_resolution_clock::now();+ if (now - globalStart > maxRunTime) {+ break;+ }+ }++ // Sort results by running time+ std::sort(+ trialResults.begin(),+ trialResults.begin() + actualTrials,+ [](const TrialResultType& a, const TrialResultType& b) {+ return a.first < b.first;+ });++ const auto getPercentile = [](size_t count, double p) -> size_t {+ return static_cast<size_t>(count * p);+ };++ const size_t trialP25 = getPercentile(actualTrials, 0.25);+ const size_t trialP75 = getPercentile(actualTrials, 0.75);+ if (trialP75 - trialP25 == 0) {+ // Use first trial results if p75 == p25.+ return std::make_pair(trialResults[0].first, trialResults[0].second);+ }++ double geomeanNsec = 0.0;+ for (size_t tryId = trialP25; tryId < trialP75; tryId++) {+ geomeanNsec += std::log(trialResults[tryId].first);+ }+ geomeanNsec = std::exp(geomeanNsec / (1.0 * (trialP75 - trialP25)));++ return std::make_pair(+ geomeanNsec, trialResults[trialP25 + (trialP75 - trialP25) / 2].second);+}++static std::pair<double, UserCounters> runProfilingGetNSPerIteration(+ const BenchmarkFun& fun, const double globalBaseline) {+ using std::chrono::duration_cast;+ using std::chrono::high_resolution_clock;+ using std::chrono::nanoseconds;++ // They key here is accuracy; too low numbers means the accuracy was+ // coarse. We up the ante until we get to at least minNanoseconds+ // timings.+ static_assert(+ std::is_same<high_resolution_clock::duration, nanoseconds>::value,+ "High resolution clock must be nanosecond resolution.");++ // This is a very simple measurement with a single epoch+ // and should be used only for profiling purposes+ detail::TimeIterData timeIterData = fun(FLAGS_bm_profile_iters);++ auto nsecs = duration_cast<nanoseconds>(timeIterData.duration);+ auto nsecIter = double(nsecs.count()) / timeIterData.niter - globalBaseline;+ return std::make_pair(nsecIter, std::move(timeIterData.userCounters));+}++struct ScaleInfo {+ double boundary;+ const char* suffix;+};++static const ScaleInfo kTimeSuffixes[]{+ {365.25 * 24 * 3600, "years"},+ {24 * 3600, "days"},+ {3600, "hr"},+ {60, "min"},+ {1, "s"},+ {1E-3, "ms"},+ {1E-6, "us"},+ {1E-9, "ns"},+ {1E-12, "ps"},+ {1E-15, "fs"},+ {0, nullptr},+};++static const ScaleInfo kMetricSuffixes[]{+ {1E24, "Y"}, // yotta+ {1E21, "Z"}, // zetta+ {1E18, "X"}, // "exa" written with suffix 'X' so as to not create+ // confusion with scientific notation+ {1E15, "P"}, // peta+ {1E12, "T"}, // terra+ {1E9, "G"}, // giga+ {1E6, "M"}, // mega+ {1E3, "K"}, // kilo+ {1, ""},+ {1E-3, "m"}, // milli+ {1E-6, "u"}, // micro+ {1E-9, "n"}, // nano+ {1E-12, "p"}, // pico+ {1E-15, "f"}, // femto+ {1E-18, "a"}, // atto+ {1E-21, "z"}, // zepto+ {1E-24, "y"}, // yocto+ {0, nullptr},+};++static string humanReadable(+ double n, unsigned int decimals, const ScaleInfo* scales) {+ if (std::isinf(n) || std::isnan(n)) {+ return folly::to<string>(n);+ }++ const double absValue = fabs(n);+ const ScaleInfo* scale = scales;+ while (absValue < scale[0].boundary && scale[1].suffix != nullptr) {+ ++scale;+ }++ const double scaledValue = n / scale->boundary;+ return stringPrintf("%.*f%s", decimals, scaledValue, scale->suffix);+}++static string readableTime(double n, unsigned int decimals) {+ return humanReadable(n, decimals, kTimeSuffixes);+}++static string metricReadable(double n, unsigned int decimals) {+ return humanReadable(n, decimals, kMetricSuffixes);+}++namespace {++constexpr std::string_view kUnitHeaders = "relative time/iter iters/s";+constexpr std::string_view kUnitHeadersPadding = " ";+void printHeaderContents(std::string_view file) {+ printf(+ "%-.*s%*s%*s",+ static_cast<int>(file.size()),+ file.data(),+ static_cast<int>(kUnitHeadersPadding.size()),+ kUnitHeadersPadding.data(),+ static_cast<int>(kUnitHeaders.size()),+ kUnitHeaders.data());+}++void printDefaultHeaderContents(std::string_view file, size_t columns) {+ const size_t maxFileNameChars =+ columns - kUnitHeaders.size() - kUnitHeadersPadding.size();++ if (file.size() <= maxFileNameChars) {+ printHeaderContents(file);+ } else {+ std::string truncatedFile = std::string(file.begin(), file.end());+ constexpr std::string_view overflowFilePrefix = "[...]";+ const auto overflow = truncatedFile.size() - maxFileNameChars;+ truncatedFile.erase(0, overflow);+ truncatedFile.replace(0, overflowFilePrefix.size(), overflowFilePrefix);+ printHeaderContents(truncatedFile);+ }+}++void printSeparator(char pad, unsigned int columns) {+ puts(string(columns, pad).c_str());+}++class BenchmarkResultsPrinter {+ public:+ BenchmarkResultsPrinter() : columns_(FLAGS_bm_result_width_chars) {}+ explicit BenchmarkResultsPrinter(std::set<std::string> counterNames)+ : counterNames_(std::move(counterNames)),+ namesLength_{std::accumulate(+ counterNames_.begin(),+ counterNames_.end(),+ size_t{0},+ [](size_t acc, auto&& name) { return acc + 2 + name.length(); })},+ columns_(FLAGS_bm_result_width_chars + namesLength_) {}++ void separator(char pad) { printSeparator(pad, columns_); }++ void header(std::string_view file) {+ separator('=');+ printDefaultHeaderContents(file, columns_ - namesLength_);++ for (auto const& name : counterNames_) {+ printf(" %s", name.c_str());+ }+ printf("\n");+ separator('=');+ }++ void print(const vector<detail::BenchmarkResult>& data) {+ for (auto& datum : data) {+ auto file = datum.file;+ if (file != lastFile_) {+ // New file starting+ header(file);+ lastFile_ = file;+ }++ string s = datum.name;+ if (s == "-") {+ // Simply draw a line across the benchmark results+ separator('-');+ continue;+ }+ if (s[0] == '"') {+ // Simply print some text. Strips implied quote characters+ // from the beginning and end of the name.+ printf("%s\n", s.substr(1, s.length() - 2).c_str());+ continue;+ }+ bool useBaseline = false;+ // '%' indicates a relative benchmark.+ if (s[0] == '%') {+ s.erase(0, 1);+ useBaseline = isBaselineSet();+ } else {+ baselineNsPerIter_ = datum.timeInNs;+ useBaseline = false;+ }+ s.resize(columns_ - namesLength_ - kUnitHeaders.size(), ' ');++ const auto nsPerIter = datum.timeInNs;+ const auto secPerIter = nsPerIter / 1E9;+ const auto itersPerSec = (secPerIter == 0)+ ? std::numeric_limits<double>::infinity()+ : (1 / secPerIter);+ if (!useBaseline) {+ // Print without baseline+ printf(+ "%*s%8.8s %9.9s %8.8s",+ static_cast<int>(s.size()),+ s.c_str(),+ "", // Padding for "relative" header.+ readableTime(secPerIter, 2).c_str(),+ metricReadable(itersPerSec, 2).c_str());+ } else {+ // Print with baseline+ const auto rel = baselineNsPerIter_ / nsPerIter * 100.0;+ printf(+ "%*s%#7.5g%% %9.9s %8.8s",+ static_cast<int>(s.size()),+ s.c_str(),+ rel,+ readableTime(secPerIter, 2).c_str(),+ metricReadable(itersPerSec, 2).c_str());+ }+ for (auto const& name : counterNames_) {+ if (auto ptr = folly::get_ptr(datum.counters, name)) {+ switch (ptr->type) {+ // UserMetrics constructed as precision_values avoid the+ // implicit cast from long to double when formatting the output+ case UserMetric::Type::TIME:+ folly::variant_match(ptr->value, [&](auto value) {+ printf(+ " %*s",+ int(name.length()),+ readableTime(value, 2).c_str());+ });+ break;+ case UserMetric::Type::METRIC:+ folly::variant_match(ptr->value, [&](auto value) {+ printf(+ " %*s",+ int(name.length()),+ metricReadable(value, 2).c_str());+ });+ break;+ case UserMetric::Type::CUSTOM:+ default:+ folly::variant_match(ptr->value, [&](auto value) {+ printf(+ " %*" PRId64,+ int(name.length()),+ static_cast<int64_t>(value));+ });+ }+ } else {+ printf(" %*s", int(name.length()), "NaN");+ }+ }+ printf("\n");+ }+ }++ private:+ bool isBaselineSet() {+ return baselineNsPerIter_ != numeric_limits<double>::max();+ }++ std::set<std::string> counterNames_;+ size_t namesLength_{0};+ size_t columns_{0};+ double baselineNsPerIter_{numeric_limits<double>::max()};+ string lastFile_;+};+} // namespace++static void printBenchmarkResultsAsJson(+ const vector<detail::BenchmarkResult>& data) {+ dynamic d = dynamic::object;+ for (auto& datum : data) {+ d[datum.name] = datum.timeInNs * 1000.;+ }++ printf("%s\n", toPrettyJson(d).c_str());+}++void benchmarkResultsToDynamic(+ const vector<detail::BenchmarkResult>& data, dynamic& out) {+ out = dynamic::array;+ for (auto& datum : data) {+ if (!datum.counters.empty()) {+ dynamic obj = dynamic::object;+ for (auto& counter : datum.counters) {+ dynamic counterInfo = dynamic::object;+ folly::variant_match(counter.second.value, [&](auto value) {+ counterInfo["value"] = value;+ });+ counterInfo["type"] = static_cast<int>(counter.second.type);+ obj[counter.first] = counterInfo;+ }+ out.push_back(+ dynamic::array(datum.file, datum.name, datum.timeInNs, obj));+ } else {+ out.push_back(dynamic::array(datum.file, datum.name, datum.timeInNs));+ }+ }+}++void benchmarkResultsFromDynamic(+ const dynamic& d, vector<detail::BenchmarkResult>& results) {+ for (auto& datum : d) {+ results.push_back(+ {datum[0].asString(),+ datum[1].asString(),+ datum[2].asDouble(),+ UserCounters{}});+ }+}++static pair<StringPiece, StringPiece> resultKey(+ const detail::BenchmarkResult& result) {+ return pair<StringPiece, StringPiece>(result.file, result.name);+}++void printResultComparison(+ const vector<detail::BenchmarkResult>& base,+ const vector<detail::BenchmarkResult>& test) {+ map<pair<StringPiece, StringPiece>, double> baselines;++ for (auto& baseResult : base) {+ baselines[resultKey(baseResult)] = baseResult.timeInNs;+ }++ // Width available+ const size_t columns = FLAGS_bm_result_width_chars;++ auto header = [&](const string_view& file) {+ printSeparator('=', columns);+ printDefaultHeaderContents(file, columns);+ printf("\n");+ printSeparator('=', columns);+ };++ string lastFile;++ for (auto& datum : test) {+ folly::Optional<double> baseline =+ folly::get_optional(baselines, resultKey(datum));+ auto file = datum.file;+ if (file != lastFile) {+ // New file starting+ header(file);+ lastFile = file;+ }++ string s = datum.name;+ if (s == "-") {+ printSeparator('-', columns);+ continue;+ }+ if (s[0] == '%') {+ s.erase(0, 1);+ }+ s.resize(columns - 29, ' ');+ auto nsPerIter = datum.timeInNs;+ auto secPerIter = nsPerIter / 1E9;+ auto itersPerSec = (secPerIter == 0)+ ? std::numeric_limits<double>::infinity()+ : (1 / secPerIter);+ if (!baseline) {+ // Print without baseline+ printf(+ "%*s %9s %7s\n",+ static_cast<int>(s.size()),+ s.c_str(),+ readableTime(secPerIter, 2).c_str(),+ metricReadable(itersPerSec, 2).c_str());+ } else {+ // Print with baseline+ auto rel = *baseline / nsPerIter * 100.0;+ printf(+ "%*s %7.2f%% %9s %7s\n",+ static_cast<int>(s.size()),+ s.c_str(),+ rel,+ readableTime(secPerIter, 2).c_str(),+ metricReadable(itersPerSec, 2).c_str());+ }+ }+ printSeparator('=', columns);+}++void checkRunMode() {+ if (folly::kIsDebug || folly::kIsSanitize) {+ std::cerr << "WARNING: Benchmark running "+ << (folly::kIsDebug ? "in DEBUG mode" : "with SANITIZERS")+ << std::endl;+ }+}++namespace {++struct BenchmarksToRun {+ const detail::BenchmarkRegistration* baseline = nullptr;+ const detail::BenchmarkRegistration* suspenderBaseline = nullptr;+ std::vector<const detail::BenchmarkRegistration*> benchmarks;+ std::vector<size_t> separatorsAfter;+};++void addSeparator(BenchmarksToRun& res) {+ size_t separatorAfter = res.benchmarks.size();+ if (separatorAfter == 0) {+ return;+ }+ if (res.separatorsAfter.empty() ||+ res.separatorsAfter.back() != (separatorAfter - 1)) {+ res.separatorsAfter.push_back(res.benchmarks.size() - 1);+ }+}++BenchmarksToRun selectBenchmarksToRun(+ const std::vector<detail::BenchmarkRegistration>& benchmarks) {+ BenchmarksToRun res;++ folly::Optional<boost::regex> bmRegex;+ folly::Optional<boost::regex> bmFileRegex;++ res.benchmarks.reserve(benchmarks.size());++ if (!FLAGS_bm_regex.empty()) {+ bmRegex.emplace(FLAGS_bm_regex);+ }++ if (!FLAGS_bm_file_regex.empty()) {+ bmFileRegex.emplace(FLAGS_bm_file_regex);+ }++ for (auto& bm : benchmarks) {+ if (bm.name == "-") {+ addSeparator(res);+ continue;+ }++ if (bm.name == kGlobalBenchmarkBaseline) {+ res.baseline = &bm;+ continue;+ }++ if (bm.name == kGlobalBenchmarkSuspenderBaseline) {+ res.suspenderBaseline = &bm;+ continue;+ }++ bool matchedName = !bmRegex || boost::regex_search(bm.name, *bmRegex);+ bool matchedFile =+ !bmFileRegex || boost::regex_search(bm.file, *bmFileRegex);++ if (matchedName && matchedFile) {+ res.benchmarks.push_back(&bm);+ }+ }++ if (bmRegex) {+ res.separatorsAfter.clear();+ }++ CHECK(res.baseline);++ return res;+}++void maybeRunWarmUpIteration(const BenchmarksToRun& toRun) {+ bool shouldRun = FLAGS_bm_warm_up_iteration;++#if FOLLY_PERF_IS_SUPPORTED+ shouldRun = shouldRun || !FLAGS_bm_perf_args.empty();+#endif++ if (!shouldRun) {+ return;+ }++ for (const auto* bm : toRun.benchmarks) {+ bm->func(1);+ }+}++class ShouldDrawLineTracker {+ public:+ explicit ShouldDrawLineTracker(const BenchmarksToRun& toRun)+ : separatorsAfter_(&toRun.separatorsAfter) {}++ bool operator()() {+ std::size_t i = curI_++;+ if (drawAfterI_ >= separatorsAfter_->size()) {+ return false;+ }+ std::size_t nextToDrawAfter = (*separatorsAfter_)[drawAfterI_];+ if (i == nextToDrawAfter) {+ ++drawAfterI_;+ return true;+ }+ return false;+ }++ private:+ const std::vector<std::size_t>* separatorsAfter_;+ std::size_t curI_ = 0;+ std::size_t drawAfterI_ = 0;+};++std::pair<std::set<std::string>, std::vector<detail::BenchmarkResult>>+runBenchmarksWithPrinterImpl(+ BenchmarkResultsPrinter* FOLLY_NULLABLE printer,+ const BenchmarksToRun& toRun) {+ vector<detail::BenchmarkResult> results;+ results.reserve(toRun.benchmarks.size());++ // PLEASE KEEP QUIET. MEASUREMENTS IN PROGRESS.++ auto const globalBaseline =+ runBenchmarkGetNSPerIteration(toRun.baseline->func, 0);++ auto const globalSuspenderBaseline =+ runBenchmarkGetNSPerIteration(toRun.suspenderBaseline->func, 0);++ BenchmarkSuspender::suspenderOverhead = chrono::nanoseconds(+ static_cast<chrono::high_resolution_clock::rep>(+ globalSuspenderBaseline.first));++ std::set<std::string> counterNames;+ ShouldDrawLineTracker shouldDrawLineTracker(toRun);+ for (std::size_t i = 0; i != toRun.benchmarks.size(); ++i) {+ std::pair<double, UserCounters> elapsed;+ const detail::BenchmarkRegistration& bm = *toRun.benchmarks[i];+ bool shouldDrawLineAfter = shouldDrawLineTracker();++ if (FLAGS_bm_profile) {+ elapsed = runProfilingGetNSPerIteration(bm.func, globalBaseline.first);+ } else {+ elapsed = FLAGS_bm_estimate_time+ ? runBenchmarkGetNSPerIterationEstimate(bm.func, globalBaseline.first)+ : runBenchmarkGetNSPerIteration(bm.func, globalBaseline.first);+ }++ // if customized user counters is used, it cannot print the result in real+ // time as it needs to run all cases first to know the complete set of+ // counters have been used, then the header can be printed out properly+ if (printer != nullptr) {+ printer->print({{bm.file, bm.name, elapsed.first, elapsed.second}});+ if (shouldDrawLineAfter) {+ printer->separator('-');+ }+ }+ results.push_back({bm.file, bm.name, elapsed.first, elapsed.second});++ // get all counter names+ for (auto const& kv : elapsed.second) {+ counterNames.insert(kv.first);+ }+ }++ // MEASUREMENTS DONE.++ return std::make_pair(std::move(counterNames), std::move(results));+}++std::vector<detail::BenchmarkResult> resultsFromFile(+ const std::string& filename) {+ std::string content;+ readFile(filename.c_str(), content);+ std::vector<detail::BenchmarkResult> ret;+ if (!content.empty()) {+ benchmarkResultsFromDynamic(parseJson(content), ret);+ }+ return ret;+}++bool writeResultsToFile(+ const std::vector<detail::BenchmarkResult>& results,+ const std::string& filename) {+ dynamic d;+ benchmarkResultsToDynamic(results, d);+ return writeFile(toPrettyJson(d), filename.c_str());+}++} // namespace++namespace detail {++std::ostream& operator<<(std::ostream& os, const BenchmarkResult& x) {+ folly::dynamic r;+ benchmarkResultsToDynamic({x}, r);+ return os << r[0];+}++bool operator==(const BenchmarkResult& x, const BenchmarkResult& y) {+ auto xtime = static_cast<std::uint64_t>(x.timeInNs * 1000);+ auto ytime = static_cast<std::uint64_t>(y.timeInNs * 1000);+ return x.name == y.name && x.file == y.file && xtime == ytime &&+ x.counters == y.counters;+}++std::chrono::high_resolution_clock::duration BenchmarkSuspenderBase::timeSpent;+std::chrono::high_resolution_clock::duration+ BenchmarkSuspenderBase::suspenderOverhead;++void BenchmarkingStateBase::addBenchmarkImpl(+ std::string file, std::string name, BenchmarkFun fun, bool useCounter) {+ std::lock_guard guard(mutex_);+ benchmarks_.push_back(+ {std::move(file), std::move(name), std::move(fun), useCounter});+}++bool BenchmarkingStateBase::useCounters() const {+ std::lock_guard guard(mutex_);+ return std::any_of(+ benchmarks_.begin(), benchmarks_.end(), [](const auto& bm) {+ return bm.useCounter;+ });+}++std::vector<std::string> BenchmarkingStateBase::getBenchmarkList() {+ std::vector<std::string> bmNames;+ auto toRun = selectBenchmarksToRun(benchmarks_);+ for (auto benchmarkRegistration : toRun.benchmarks) {+ bmNames.push_back(benchmarkRegistration->name);+ }++ return bmNames;+}++// static+folly::StringPiece BenchmarkingStateBase::getGlobalBaselineNameForTests() {+ return kGlobalBenchmarkBaseline;+}++folly::StringPiece+BenchmarkingStateBase::getGlobalSuspenderBaselineNameForTests() {+ return kGlobalBenchmarkSuspenderBaseline;+}++PerfScoped BenchmarkingStateBase::doSetUpPerfScoped(+ const std::vector<std::string>& args) const {+ return PerfScoped{args};+}++PerfScoped BenchmarkingStateBase::setUpPerfScoped() const {+ std::vector<std::string> perfArgs;+#if FOLLY_PERF_IS_SUPPORTED+ folly::split(' ', FLAGS_bm_perf_args, perfArgs, true);+#endif+ if (perfArgs.empty()) {+ return PerfScoped{};+ }+ return doSetUpPerfScoped(perfArgs);+}++template <typename Printer>+std::pair<std::set<std::string>, std::vector<BenchmarkResult>>+BenchmarkingStateBase::runBenchmarksWithPrinter(Printer* printer) const {+ std::lock_guard guard(mutex_);+ BenchmarksToRun toRun = selectBenchmarksToRun(benchmarks_);+ maybeRunWarmUpIteration(toRun);++ detail::PerfScoped perf = setUpPerfScoped();+ return runBenchmarksWithPrinterImpl(printer, toRun);+}++std::vector<BenchmarkResult> BenchmarkingStateBase::runBenchmarksWithResults()+ const {+ return runBenchmarksWithPrinter(+ static_cast<BenchmarkResultsPrinter*>(nullptr))+ .second;+}++std::vector<BenchmarkResult> runBenchmarksWithResults() {+ return globalBenchmarkState().runBenchmarksWithResults();+}++} // namespace detail++void runBenchmarks() {+ auto& state = detail::globalBenchmarkState();++ if (FLAGS_bm_list) {+ auto bmNames = state.getBenchmarkList();+ for (auto testName : bmNames) {+ std::cout << testName << std::endl;+ }+ return;+ }++ if (FLAGS_bm_profile) {+ printf(+ "WARNING: Running with constant number of iterations. Results might be jittery.\n");+ }++ if (FLAGS_bm_min_iters >= FLAGS_bm_max_iters) {+ std::cerr << "WARNING: bm_min_iters > bm_max_iters; increasing the max"+ << std::endl;+ FLAGS_bm_max_iters = FLAGS_bm_min_iters + 1;+ }++ checkRunMode();++ BenchmarkResultsPrinter printer;+ bool useCounter = state.useCounters();++ // PLEASE KEEP QUIET. MEASUREMENTS IN PROGRESS.++ const bool shouldPrintInline =+ FLAGS_bm_relative_to.empty() && !FLAGS_json && !useCounter;+ auto benchmarkResults =+ state.runBenchmarksWithPrinter(shouldPrintInline ? &printer : nullptr);++ // PLEASE MAKE NOISE. MEASUREMENTS DONE.++ if (FLAGS_json) {+ printBenchmarkResultsAsJson(benchmarkResults.second);+ } else if (!FLAGS_bm_relative_to.empty()) {+ printResultComparison(+ resultsFromFile(FLAGS_bm_relative_to), benchmarkResults.second);+ } else if (!shouldPrintInline) {+ printer = BenchmarkResultsPrinter{std::move(benchmarkResults.first)};+ printer.print(benchmarkResults.second);+ printer.separator('=');+ }++ if (!FLAGS_bm_json_verbose.empty()) {+ writeResultsToFile(benchmarkResults.second, FLAGS_bm_json_verbose);+ }++ checkRunMode();+}++} // namespace folly
@@ -33,6 +33,7 @@ #include <limits> #include <mutex> #include <set>+#include <string> #include <type_traits> #include <unordered_map> #include <variant>@@ -42,6 +43,8 @@ FOLLY_GFLAGS_DECLARE_bool(benchmark); FOLLY_GFLAGS_DECLARE_uint32(bm_result_width_chars);+FOLLY_GFLAGS_DECLARE_int32(bm_min_iters);+FOLLY_GFLAGS_DECLARE_int64(bm_max_iters); namespace folly { @@ -215,7 +218,7 @@ bool useCounters() const; void addBenchmarkImpl(- const char* file, StringPiece name, BenchmarkFun, bool useCounter);+ std::string file, std::string name, BenchmarkFun, bool useCounter); std::vector<std::string> getBenchmarkList(); @@ -238,7 +241,7 @@ public: template <typename Lambda> typename std::enable_if<folly::is_invocable_v<Lambda, unsigned>>::type- addBenchmark(const char* file, StringPiece name, Lambda&& lambda) {+ addBenchmark(std::string file, std::string name, Lambda&& lambda) { auto execute = [=](unsigned int times) { BenchmarkSuspender<Clock>::timeSpent = {}; unsigned int niter;@@ -254,13 +257,14 @@ UserCounters{}}; }; - this->addBenchmarkImpl(file, name, detail::BenchmarkFun(execute), false);+ this->addBenchmarkImpl(+ std::move(file), std::move(name), detail::BenchmarkFun(execute), false); } template <typename Lambda> typename std::enable_if<folly::is_invocable_v<Lambda>>::type addBenchmark(- const char* file, StringPiece name, Lambda&& lambda) {- addBenchmark(file, name, [=](unsigned int times) {+ std::string file, std::string name, Lambda&& lambda) {+ addBenchmark(std::move(file), std::move(name), [=](unsigned int times) { unsigned int niter = 0; while (times-- > 0) { niter += lambda();@@ -272,7 +276,7 @@ template <typename Lambda> typename std::enable_if< folly::is_invocable_v<Lambda, UserCounters&, unsigned>>::type- addBenchmark(const char* file, StringPiece name, Lambda&& lambda) {+ addBenchmark(std::string file, std::string name, Lambda&& lambda) { auto execute = [=](unsigned int times) { BenchmarkSuspender<Clock>::timeSpent = {}; unsigned int niter;@@ -290,22 +294,25 @@ }; this->addBenchmarkImpl(- file,- name,+ std::move(file),+ std::move(name), std::function<detail::TimeIterData(unsigned int)>(execute), true); } template <typename Lambda> typename std::enable_if<folly::is_invocable_v<Lambda, UserCounters&>>::type- addBenchmark(const char* file, StringPiece name, Lambda&& lambda) {- addBenchmark(file, name, [=](UserCounters& counters, unsigned int times) {- unsigned int niter = 0;- while (times-- > 0) {- niter += lambda(counters);- }- return niter;- });+ addBenchmark(std::string file, std::string name, Lambda&& lambda) {+ addBenchmark(+ std::move(file),+ std::move(name),+ [=](UserCounters& counters, unsigned int times) {+ unsigned int niter = 0;+ while (times-- > 0) {+ niter += lambda(counters);+ }+ return niter;+ }); } }; @@ -323,8 +330,9 @@ * is not. */ inline void addBenchmarkImpl(- const char* file, StringPiece name, BenchmarkFun f, bool useCounter) {- globalBenchmarkState().addBenchmarkImpl(file, name, std::move(f), useCounter);+ std::string file, std::string name, BenchmarkFun f, bool useCounter) {+ globalBenchmarkState().addBenchmarkImpl(+ std::move(file), std::move(name), std::move(f), useCounter); } } // namespace detail@@ -350,8 +358,9 @@ * as their first parameter. */ template <typename Lambda>-void addBenchmark(const char* file, StringPiece name, Lambda&& lambda) {- detail::globalBenchmarkState().addBenchmark(file, name, lambda);+void addBenchmark(std::string file, std::string name, Lambda&& lambda) {+ detail::globalBenchmarkState().addBenchmark(+ std::move(file), std::move(name), lambda); } struct dynamic;@@ -373,24 +382,29 @@ * friends below. */ -#define BENCHMARK_IMPL(funName, stringName, rv, paramType, paramName) \- static void funName(paramType); \- [[maybe_unused]] static bool FB_ANONYMOUS_VARIABLE(follyBenchmarkUnused) = \- (::folly::addBenchmark( \- __FILE__, \- stringName, \- [](paramType paramName) -> unsigned { \- funName(paramName); \- return rv; \- }), \- true); \+FOLLY_PUSH_WARNING+FOLLY_CLANG_DISABLE_WARNING("-Wglobal-constructors")++#define BENCHMARK_IMPL(funName, stringName, rv, paramType, paramName) \+ static void funName(paramType); \+ [[maybe_unused]] static const bool FB_ANONYMOUS_VARIABLE( \+ follyBenchmarkUnused) = \+ (::folly::addBenchmark( \+ __FILE__, \+ stringName, \+ [](paramType paramName) -> unsigned { \+ funName(paramName); \+ return rv; \+ }), \+ true); \ static void funName(paramType paramName) #define BENCHMARK_IMPL_COUNTERS( \ funName, stringName, counters, rv, paramType, paramName) \ static void funName( \ ::folly::UserCounters& FOLLY_PP_DETAIL_APPEND_VA_ARG(paramType)); \- [[maybe_unused]] static bool FB_ANONYMOUS_VARIABLE(follyBenchmarkUnused) = \+ [[maybe_unused]] static const bool FB_ANONYMOUS_VARIABLE( \+ follyBenchmarkUnused) = \ (::folly::addBenchmark( \ __FILE__, \ stringName, \@@ -409,16 +423,19 @@ * number of iterations. Used internally, see BENCHMARK_MULTI and friends * below. */-#define BENCHMARK_MULTI_IMPL(funName, stringName, paramType, paramName) \- static unsigned funName(paramType); \- [[maybe_unused]] static bool FB_ANONYMOUS_VARIABLE(follyBenchmarkUnused) = \- (::folly::addBenchmark( \- __FILE__, \- stringName, \- [](paramType paramName) { return funName(paramName); }), \- true); \+#define BENCHMARK_MULTI_IMPL(funName, stringName, paramType, paramName) \+ static unsigned funName(paramType); \+ [[maybe_unused]] static const bool FB_ANONYMOUS_VARIABLE( \+ follyBenchmarkUnused) = \+ (::folly::addBenchmark( \+ __FILE__, \+ stringName, \+ [](paramType paramName) { return funName(paramName); }), \+ true); \ static unsigned funName(paramType paramName) +FOLLY_POP_WARNING+ /** * Introduces a benchmark function. Use with either one or two arguments. * The first is the name of the benchmark. Use something descriptive, such@@ -661,7 +678,8 @@ * Draws a line of dashes. */ #define BENCHMARK_DRAW_LINE() \- [[maybe_unused]] static bool FB_ANONYMOUS_VARIABLE(follyBenchmarkUnused) = \+ [[maybe_unused]] static const bool FB_ANONYMOUS_VARIABLE( \+ follyBenchmarkUnused) = \ (::folly::addBenchmark(__FILE__, "-", []() -> unsigned { return 0; }), \ true) @@ -669,12 +687,13 @@ * Prints arbitrary text. */ #define BENCHMARK_DRAW_TEXT(text) \- [[maybe_unused]] static bool FB_ANONYMOUS_VARIABLE(follyBenchmarkUnused) = \+ [[maybe_unused]] static const bool FB_ANONYMOUS_VARIABLE( \+ follyBenchmarkUnused) = \ (::folly::addBenchmark(__FILE__, #text, []() -> unsigned { return 0; }), \ true) /**- * Allows execution of code that doesn't count torward the benchmark's+ * Allows execution of code that doesn't count toward the benchmark's * time budget. Example: * * BENCHMARK_START_GROUP(insertVectorBegin, n) {
@@ -0,0 +1,149 @@+/*+ * Copyright (c) Meta Platforms, Inc. and affiliates.+ *+ * Licensed under the Apache License, Version 2.0 (the "License");+ * you may not use this file except in compliance with the License.+ * You may obtain a copy of the License at+ *+ * http://www.apache.org/licenses/LICENSE-2.0+ *+ * Unless required by applicable law or agreed to in writing, software+ * distributed under the License is distributed on an "AS IS" BASIS,+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.+ * See the License for the specific language governing permissions and+ * limitations under the License.+ */++#include <folly/BenchmarkUtil.h>++#include <array> // @manual+#include <filesystem>+#include <fstream>+#include <string>++#include <fmt/format.h>++#include <folly/lang/Align.h>+#include <folly/portability/Sched.h>+#include <folly/portability/Windows.h>+#include <folly/system/arch/x86.h>++#if defined(__APPLE__) || defined(__FreeBSD__)+#include <sys/sysctl.h>+#endif++namespace folly {++namespace detail {++size_t bm_llc_size_fallback() {+#if defined(__linux__)++ /// sysctl is deprecated on Linux; walk sysfs++ int cpu = sched_getcpu();+ if (cpu < 0) {+ return 0;+ }++ auto dir = std::filesystem::path(+ fmt::format("/sys/devices/system/cpu/cpu{}/cache", cpu));++ size_t size = 0;+ for (const auto& entry : std::filesystem::directory_iterator(dir)) {+ if (entry.path().filename().native().starts_with("index")) {+ std::ifstream size_ifs(entry.path() / "size");+ std::string size_str;+ size_ifs >> size_str;+ size_t size_num = std::stoul(size_str);+ if (size_str.back() == 'K') {+ size_num *= 1024;+ } else if (size_str.back() == 'M') {+ size_num *= 1024 * 1024;+ }+ size = std::max(size, size_num);+ }+ }++ return size;++#elif defined(__APPLE__) || defined(__FreeBSD__)++ auto names = std::array{+ "hw.l3cachesize", // FreeBSD, Intel Mac+ "hw.l2cachesize", // Apple Silicon+ };+ for (auto name : names) {+ uint64_t size = 0;+ size_t len = sizeof(size);+ if (sysctlbyname(name, &size, &len, NULL, 0) == 0 && size > 0) {+ return size;+ }+ }++ return 0;++#elif defined(_WIN32)++ DWORD length = 0;+ GetLogicalProcessorInformation(nullptr, &length);+ if (GetLastError() != ERROR_INSUFFICIENT_BUFFER || length == 0) {+ return 0;+ }+ if (length % sizeof(SYSTEM_LOGICAL_PROCESSOR_INFORMATION) != 0) {+ return 0;+ }++ size_t const count = length / sizeof(SYSTEM_LOGICAL_PROCESSOR_INFORMATION);+ std::vector<SYSTEM_LOGICAL_PROCESSOR_INFORMATION> infos(count);+ if (!GetLogicalProcessorInformation(infos.data(), &length)) {+ return 0;+ }++ size_t size = 0;+ for (auto const& info : infos) {+ if (info.Relationship == RelationCache) {+ size = std::max<size_t>(size, info.Cache.Size);+ }+ }+ return size;++#else++ return 0;++#endif+}++} // namespace detail++size_t bm_llc_size() {+ return kIsArchX86 || kIsArchAmd64+ ? folly::x86_cpuid_get_llc_cache_info().cache_size()+ : detail::bm_llc_size_fallback();+}++void bm_llc_evict(size_t value) {+ constexpr auto step =+ folly::hardware_constructive_interference_size / sizeof(size_t);++ constexpr auto words_per_line =+ folly::hardware_constructive_interference_size / step;++ static auto const llc_size = bm_llc_size();++ auto const llc_lines =+ llc_size / folly::hardware_constructive_interference_size;++ static std::unique_ptr<size_t volatile[]> const vec{+ new size_t[llc_lines * words_per_line]};++ // do N passes over the region, each time touching one word per line+ for (size_t j = 0; j < step; ++j) {+ for (size_t i = 0; i < llc_lines; i += step) {+ vec[i * words_per_line + j] = value;+ }+ }+}++} // namespace folly
@@ -44,4 +44,19 @@ compiler_must_not_predict(datum); } +namespace detail {+size_t bm_llc_size_fallback();+}++/// bm_llc_size+///+/// Calculates the size of the LLC (Last Level Cache, typically L3 on x86-64).+size_t bm_llc_size();++/// bm_llc_evict+///+/// Write to a large block of memory and evict whatever cache lines might+/// currently be resident in any levels of cache.+void bm_llc_evict(size_t value);+ } // namespace folly
@@ -47,6 +47,14 @@ #endif #endif +/// FOLLY_GLIBC_PREREQ+#if !defined(__GLIBC__)+#define FOLLY_GLIBC_PREREQ(maj, min) 0+#else+#define FOLLY_GLIBC_PREREQ(maj, min) \+ (__GLIBC__ > (maj)) || (__GLIBC__ == (maj) && __GLIBC_MINOR__ >= (min))+#endif+ #if defined(__has_builtin) #define FOLLY_HAS_BUILTIN(...) __has_builtin(__VA_ARGS__) #else
@@ -32,7 +32,7 @@ // Internal cancellation state object. class CancellationState { public:- FOLLY_NODISCARD static CancellationStateSourcePtr create();+ [[nodiscard]] static CancellationStateSourcePtr create(); protected: // Constructed initially with a CancellationSource reference count of 1.@@ -49,9 +49,9 @@ void removeSourceReference() noexcept; public:- FOLLY_NODISCARD CancellationStateTokenPtr addTokenReference() noexcept;+ [[nodiscard]] CancellationStateTokenPtr addTokenReference() noexcept; - FOLLY_NODISCARD CancellationStateSourcePtr addSourceReference() noexcept;+ [[nodiscard]] CancellationStateSourcePtr addSourceReference() noexcept; bool tryAddCallback( CancellationCallback* callback,@@ -112,7 +112,7 @@ public: template <typename... Args>- FOLLY_NODISCARD static std::+ [[nodiscard]] static std:: pair<CancellationStateSourcePtr, std::tuple<Data...>*> create(Args&&... data); @@ -442,7 +442,8 @@ } template <typename... Ts>-inline CancellationToken CancellationToken::merge(Ts&&... tokens) {+CancellationToken cancellation_token_merge_fn::operator()(+ Ts&&... tokens) const { if constexpr (sizeof...(Ts) == 0) { return CancellationToken(); } else if constexpr (sizeof...(Ts) == 1) {@@ -489,14 +490,17 @@ } return *copyToks[0]; } else if constexpr (NCopy == N) {- return CancellationToken(detail::MergingCancellationState::createCopy(- copyIdx, copyToks.data()));+ return CancellationToken(+ detail::MergingCancellationState::createCopy(+ copyIdx, copyToks.data())); } else if constexpr (NCopy == 0) {- return CancellationToken(detail::MergingCancellationState::createMove(- moveIdx, moveToks.data()));+ return CancellationToken(+ detail::MergingCancellationState::createMove(+ moveIdx, moveToks.data())); } else {- return CancellationToken(detail::MergingCancellationState::createCopyMove(- copyIdx, copyToks.data(), moveIdx, moveToks.data()));+ return CancellationToken(+ detail::MergingCancellationState::createCopyMove(+ copyIdx, copyToks.data(), moveIdx, moveToks.data())); } } }
@@ -29,6 +29,7 @@ class CancellationCallback; class CancellationSource;+struct cancellation_token_merge_fn; namespace detail { class CancellationState;@@ -107,18 +108,6 @@ bool canBeCancelled() const noexcept; /**- * Obtain a CancellationToken linked to any number of other- * CancellationTokens.- *- * This token will have cancellation requested when any of the passed-in- * tokens do.- * This token is cancellable if any of the passed-in tokens are at the time of- * construction.- */- template <typename... Ts>- static CancellationToken merge(Ts&&... tokens);-- /** * Swaps the underlying state of the cancellation token with the token that is * passed-in. */@@ -130,6 +119,7 @@ private: friend class CancellationCallback; friend class CancellationSource;+ friend struct cancellation_token_merge_fn; explicit CancellationToken(detail::CancellationStateTokenPtr state) noexcept; @@ -355,6 +345,25 @@ // thread and it is now safe to exit the destructor. std::atomic<bool> callbackCompleted_; };++/**+ * Obtain a CancellationToken linked to any number of other+ * CancellationTokens.+ *+ * This token will have cancellation requested when any of the passed-in+ * tokens do.+ * This token is cancellable if any of the passed-in tokens are at the time of+ * construction.+ *+ * Example:+ * CancellationSource a,b;+ * auto c = cancellation_token_merge(a.getToken(), b.getToken());+ */+struct cancellation_token_merge_fn {+ template <typename... Ts>+ CancellationToken operator()(Ts&&... tokens) const;+};+inline constexpr cancellation_token_merge_fn cancellation_token_merge{}; } // namespace folly
@@ -68,7 +68,7 @@ } p = dlsym(m_handle, "__vdso_clock_gettime_ns"); if (p) {- folly::chrono::clock_gettime_ns = (int64_t(*)(clockid_t))p;+ folly::chrono::clock_gettime_ns = (int64_t (*)(clockid_t))p; } }
@@ -95,14 +95,14 @@ private: // Pick the largest lock-free type available #if (ATOMIC_LLONG_LOCK_FREE == 2)- typedef unsigned long long BlockType;+ using BlockType = unsigned long long; #elif (ATOMIC_LONG_LOCK_FREE == 2) typedef unsigned long BlockType; #else // Even if not lock free, what can we do? typedef unsigned int BlockType; #endif- typedef std::atomic<BlockType> AtomicBlockType;+ using AtomicBlockType = std::atomic<BlockType>; static constexpr size_t kBitsPerBlock = std::numeric_limits<BlockType>::digits;
@@ -48,7 +48,7 @@ }; public:- typedef T value_type;+ using value_type = T; SkipListNode(const SkipListNode&) = delete; SkipListNode& operator=(const SkipListNode&) = delete;
@@ -147,16 +147,16 @@ static_assert( MAX_HEIGHT >= 2 && MAX_HEIGHT < 64, "MAX_HEIGHT can only be in the range of [2, 64)");- typedef std::unique_lock<folly::MicroSpinLock> ScopedLocker;- typedef ConcurrentSkipList<T, Comp, NodeAlloc, MAX_HEIGHT> SkipListType;+ using ScopedLocker = std::unique_lock<folly::MicroSpinLock>;+ using SkipListType = ConcurrentSkipList<T, Comp, NodeAlloc, MAX_HEIGHT>; public:- typedef detail::SkipListNode<T> NodeType;- typedef T value_type;- typedef T key_type;+ using NodeType = detail::SkipListNode<T>;+ using value_type = T;+ using key_type = T; - typedef detail::csl_iterator<value_type, NodeType> iterator;- typedef detail::csl_iterator<const value_type, NodeType> const_iterator;+ using iterator = detail::csl_iterator<value_type, NodeType>;+ using const_iterator = detail::csl_iterator<const value_type, NodeType>; class Accessor; class Skipper;@@ -535,23 +535,23 @@ template <typename T, typename Comp, typename NodeAlloc, int MAX_HEIGHT> class ConcurrentSkipList<T, Comp, NodeAlloc, MAX_HEIGHT>::Accessor {- typedef detail::SkipListNode<T> NodeType;- typedef ConcurrentSkipList<T, Comp, NodeAlloc, MAX_HEIGHT> SkipListType;+ using NodeType = detail::SkipListNode<T>;+ using SkipListType = ConcurrentSkipList<T, Comp, NodeAlloc, MAX_HEIGHT>; public:- typedef T value_type;- typedef T key_type;- typedef T& reference;- typedef T* pointer;- typedef const T& const_reference;- typedef const T* const_pointer;- typedef size_t size_type;- typedef Comp key_compare;- typedef Comp value_compare;+ using value_type = T;+ using key_type = T;+ using reference = T&;+ using pointer = T*;+ using const_reference = const T&;+ using const_pointer = const T*;+ using size_type = size_t;+ using key_compare = Comp;+ using value_compare = Comp; - typedef typename SkipListType::iterator iterator;- typedef typename SkipListType::const_iterator const_iterator;- typedef typename SkipListType::Skipper Skipper;+ using iterator = typename SkipListType::iterator;+ using const_iterator = typename SkipListType::const_iterator;+ using Skipper = typename SkipListType::Skipper; explicit Accessor(std::shared_ptr<ConcurrentSkipList> skip_list) : slHolder_(std::move(skip_list)) {@@ -673,10 +673,10 @@ ValT, std::forward_iterator_tag> { public:- typedef ValT value_type;- typedef value_type& reference;- typedef value_type* pointer;- typedef ptrdiff_t difference_type;+ using value_type = ValT;+ using reference = value_type&;+ using pointer = value_type*;+ using difference_type = ptrdiff_t; explicit csl_iterator(NodeT* node = nullptr) : node_(node) {} @@ -711,15 +711,15 @@ // Skipper interface template <typename T, typename Comp, typename NodeAlloc, int MAX_HEIGHT> class ConcurrentSkipList<T, Comp, NodeAlloc, MAX_HEIGHT>::Skipper {- typedef detail::SkipListNode<T> NodeType;- typedef ConcurrentSkipList<T, Comp, NodeAlloc, MAX_HEIGHT> SkipListType;- typedef typename SkipListType::Accessor Accessor;+ using NodeType = detail::SkipListNode<T>;+ using SkipListType = ConcurrentSkipList<T, Comp, NodeAlloc, MAX_HEIGHT>;+ using Accessor = typename SkipListType::Accessor; public:- typedef T value_type;- typedef T& reference;- typedef T* pointer;- typedef ptrdiff_t difference_type;+ using value_type = T;+ using reference = T&;+ using pointer = T*;+ using difference_type = ptrdiff_t; Skipper(std::shared_ptr<SkipListType> skipList) : accessor_(std::move(skipList)) {@@ -779,7 +779,7 @@ return succs_[0]->data(); } - value_type* operator->() {+ value_type* operator->() const { DCHECK(succs_[0] != nullptr); return &succs_[0]->data(); }
@@ -417,7 +417,7 @@ /// constexpr_mult ///-/// Multiply two values, allowing for constexpr floating-pooint overflow to+/// Multiply two values, allowing for constexpr floating-point overflow to /// infinity. template <typename T> constexpr T constexpr_mult(T const a, T const b) {@@ -513,7 +513,7 @@ /// constant evaluations. Like std::exp, which becomes constexpr in C++26. /// /// The integer overload uses iterated squaring and multiplication. The-/// floating-point overlaod naively evaluates the taylor series of exp(num)+/// floating-point overload naively evaluates the taylor series of exp(num) /// until approximate convergence. /// /// mimic: std::exp (C++23, C++26)@@ -875,7 +875,7 @@ // If Src is larger in size, we need to clamp it to valid range in Dst. Dst(constexpr_clamp(src, Src(L::min()), Src(L::max())))) // Src and Dst have different signedness.- // Check if it's signed -> unsigend cast.+ // Check if it's signed -> unsigned cast. : std::is_signed<Src>::value && std::is_unsigned<Dst>::value ? ( // If src < 0, the result should be 0.
@@ -511,7 +511,7 @@ template <class Tgt> void toAppend(__int128 value, Tgt* result) {- typedef unsigned __int128 Usrc;+ using Usrc = unsigned __int128; char buffer[detail::digitsEnough<unsigned __int128>() + 1]; const auto oute = buffer + sizeof(buffer); size_t p;@@ -613,8 +613,8 @@ typename std::enable_if< is_integral_v<Src> && IsSomeString<Tgt>::value && sizeof(Src) < 4>::type toAppend(Src value, Tgt* result) {- typedef typename std::conditional<is_signed_v<Src>, int64_t, uint64_t>::type- Intermediate;+ using Intermediate =+ typename std::conditional<is_signed_v<Src>, int64_t, uint64_t>::type; toAppend<Tgt>(static_cast<Intermediate>(value), result); } @@ -623,8 +623,8 @@ is_integral_v<Src> && sizeof(Src) < 4 && !std::is_same<Src, char>::value, size_t>::type estimateSpaceNeeded(Src value) {- typedef typename std::conditional<is_signed_v<Src>, int64_t, uint64_t>::type- Intermediate;+ using Intermediate =+ typename std::conditional<is_signed_v<Src>, int64_t, uint64_t>::type; return estimateSpaceNeeded(static_cast<Intermediate>(value)); } @@ -651,7 +651,7 @@ /// Operating mode for the floating point type version of /// `folly::ToAppend`. This is modeled after /// `double_conversion::DoubleToStringConverter::DtoaMode`.-/// Dtoa is an acryonym for Double to ASCII.+/// Dtoa is an acronym for Double to ASCII. enum class DtoaMode { /// Outputs the shortest representation of a `double`. /// The output is either in decimal or exponential notation; which ever is@@ -669,14 +669,14 @@ /// Outputs with a precision that is independent of the decimal point. /// The outputs is either decimal or exponential notation, depending on the /// value and the precision.- /// Similar to `printf`'s %g formating.+ /// Similar to `printf`'s %g formatting. /// Use the `numDigits` parameter to specify the precision. PRECISION, }; /// Flags for the floating point type version of `folly::ToAppend`. /// This is modeled after `double_conversion::DoubleToStringConverter::Flags`.-/// Dtoa is an acryonym for Double to ASCII.+/// Dtoa is an acronym for Double to ASCII. /// This enum is used to store bit wise flags, so a variable of this type may be /// a bitwise combination of these definitions. enum class DtoaFlags {@@ -737,7 +737,7 @@ return double_conversion::DoubleToStringConverter::PRECISION; default: /* unexpected */ assert(false);- // Default to PRECISION per exising behavior.+ // Default to PRECISION per existing behavior. return double_conversion::DoubleToStringConverter::PRECISION; } }@@ -1256,9 +1256,9 @@ #endif template <typename T>-typename std::- enable_if<std::is_same<T, bool>::value, Expected<T, ConversionCode>>::type- convertTo(StringPiece* src) noexcept {+typename std::enable_if<+ std::is_same<T, bool>::value,+ Expected<T, ConversionCode>>::type convertTo(StringPiece* src) noexcept { return str_to_bool(src); } @@ -1310,7 +1310,7 @@ * Parsing strings to numeric types. */ template <typename Tgt>-FOLLY_NODISCARD inline typename std::enable_if< //+[[nodiscard]] inline typename std::enable_if< // is_arithmetic_v<Tgt>, Expected<StringPiece, ConversionCode>>::type parseTo(StringPiece src, Tgt& out) {@@ -1513,7 +1513,7 @@ * } */ template <class T>-FOLLY_NODISCARD typename std::enable_if<+[[nodiscard]] typename std::enable_if< std::is_enum<T>::value, Expected<StringPiece, ConversionCode>>::type parseTo(StringPiece in, T& out) noexcept {@@ -1523,7 +1523,7 @@ return restOrError; } -FOLLY_NODISCARD+[[nodiscard]] inline Expected<StringPiece, ConversionCode> parseTo( StringPiece in, StringPiece& out) noexcept { out = in;@@ -1542,27 +1542,27 @@ } // namespace detail -FOLLY_NODISCARD+[[nodiscard]] inline Expected<StringPiece, ConversionCode> parseTo( StringPiece in, std::string& out) { return detail::parseToStr(in, out); } -FOLLY_NODISCARD+[[nodiscard]] inline Expected<StringPiece, ConversionCode> parseTo( StringPiece in, std::string_view& out) { out = std::string_view(in.data(), in.size()); return StringPiece{in.end(), in.end()}; } -FOLLY_NODISCARD+[[nodiscard]] inline Expected<StringPiece, ConversionCode> parseTo( StringPiece in, fbstring& out) { return detail::parseToStr(in, out); } template <class Str>-FOLLY_NODISCARD inline typename std::enable_if<+[[nodiscard]] inline typename std::enable_if< IsSomeString<Str>::value, Expected<StringPiece, ConversionCode>>::type parseTo(StringPiece in, Str& out) {
@@ -177,6 +177,12 @@ #define FOLLY_ATTR_GNU_RETAIN #endif +#if FOLLY_HAS_CPP_ATTRIBUTE(gnu::noclone)+#define FOLLY_ATTR_GNU_NOCLONE gnu::noclone+#else+#define FOLLY_ATTR_GNU_NOCLONE+#endif+ #if FOLLY_HAS_CPP_ATTRIBUTE(clang::lifetimebound) #define FOLLY_ATTR_CLANG_LIFETIMEBOUND clang::lifetimebound #else
@@ -20,10 +20,7 @@ #include <cstring> #include <folly/Portability.h>--#ifdef _MSC_VER-#include <intrin.h>-#endif+#include <folly/system/arch/x86.h> namespace folly { @@ -42,116 +39,23 @@ // mode since inlining happens more likely, and it doesn't happen for // statically linked binaries which don't depend on the PLT) FOLLY_ALWAYS_INLINE CpuId() {-#if defined(_MSC_VER) && (FOLLY_X64 || defined(_M_IX86))-#if !defined(__clang__)- int reg[4];- __cpuid(static_cast<int*>(reg), 0);- vendor_[0] = (uint32_t)reg[1];- vendor_[1] = (uint32_t)reg[3];- vendor_[2] = (uint32_t)reg[2];+ unsigned int reg[4];+ x86_cpuid(reg, 0);+ vendor_[0] = reg[1];+ vendor_[1] = reg[3];+ vendor_[2] = reg[2]; const int n = reg[0]; if (n >= 1) {- __cpuid(static_cast<int*>(reg), 1);- f1c_ = uint32_t(reg[2]);- f1d_ = uint32_t(reg[3]);- }- if (n >= 7) {- __cpuidex(static_cast<int*>(reg), 7, 0);- f7b_ = uint32_t(reg[1]);- f7c_ = uint32_t(reg[2]);- f7d_ = uint32_t(reg[3]);- }-#else- // Clang compiler has a bug (fixed in https://reviews.llvm.org/D101338) in- // which the `__cpuid` intrinsic does not save and restore `rbx` as it needs- // to due to being a reserved register. So in that case, do the `cpuid`- // ourselves. Clang supports inline assembly anyway.- uint32_t n;- uint32_t v0b, v0d, v0c;- __asm__(- "pushq %%rbx\n\t"- "cpuid\n\t"- "movl %%ebx, %1\n\t"- "popq %%rbx\n\t"- : "=a"(n), "=r"(v0b), "=d"(v0d), "=c"(v0c)- : "a"(0));- vendor_[0] = v0b;- vendor_[1] = v0d;- vendor_[2] = v0c;- if (n >= 1) {- uint32_t f1a;- __asm__(- "pushq %%rbx\n\t"- "cpuid\n\t"- "popq %%rbx\n\t"- : "=a"(f1a), "=c"(f1c_), "=d"(f1d_)- : "a"(1)- :);- }- if (n >= 7) {- __asm__(- "pushq %%rbx\n\t"- "cpuid\n\t"- "movq %%rbx, %%rax\n\t"- "popq %%rbx"- : "=a"(f7b_), "=c"(f7c_), "=d"(f7d_)- : "a"(7), "c"(0));- }-#endif-#elif defined(__i386__) && defined(__PIC__) && !defined(__clang__) && \- defined(__GNUC__)- // The following block like the normal cpuid branch below, but gcc- // reserves ebx for use of its pic register so we must specially- // handle the save and restore to avoid clobbering the register- uint32_t n;- uint32_t v0b, v0d, v0c;- __asm__(- "pushl %%ebx\n\t"- "cpuid\n\t"- "movl %%ebx, %1\n\t"- "popl %%ebx\n\t"- : "=a"(n), "=r"(v0b), "=d"(v0d), "=c"(v0c)- : "a"(0));- vendor_[0] = v0b;- vendor_[1] = v0d;- vendor_[2] = v0c;- if (n >= 1) {- uint32_t f1a;- __asm__(- "pushl %%ebx\n\t"- "cpuid\n\t"- "popl %%ebx\n\t"- : "=a"(f1a), "=c"(f1c_), "=d"(f1d_)- : "a"(1)- :);- }- if (n >= 7) {- __asm__(- "pushl %%ebx\n\t"- "cpuid\n\t"- "movl %%ebx, %%eax\n\t"- "popl %%ebx"- : "=a"(f7b_), "=c"(f7c_), "=d"(f7d_)- : "a"(7), "c"(0));- }-#elif FOLLY_X64 || defined(__i386__)- uint32_t n;- uint32_t v0b, v0d, v0c;- __asm__("cpuid" : "=a"(n), "=b"(v0b), "=d"(v0d), "=c"(v0c) : "a"(0));- vendor_[0] = v0b;- vendor_[1] = v0d;- vendor_[2] = v0c;- if (n >= 1) {- uint32_t f1a;- __asm__("cpuid" : "=a"(f1a), "=c"(f1c_), "=d"(f1d_) : "a"(1) : "ebx");+ x86_cpuid(reg, 1);+ f1c_ = reg[2];+ f1d_ = reg[3]; } if (n >= 7) {- uint32_t f7a;- __asm__("cpuid"- : "=a"(f7a), "=b"(f7b_), "=c"(f7c_), "=d"(f7d_)- : "a"(7), "c"(0));+ x86_cpuid(reg, 7);+ f7b_ = reg[1];+ f7c_ = reg[2];+ f7d_ = reg[3]; }-#endif } #define FOLLY_DETAIL_CPUID_X(name, r, bit) \
@@ -20,13 +20,12 @@ #include <cstring> #include <folly/CPortability.h>-#include <folly/CppAttributes.h> #include <folly/Utility.h> #include <folly/functional/Invoke.h> #include <folly/lang/CString.h> #if __has_include(<cxxabi.h>)-#include <cxxabi.h>+#include <cxxabi.h> // @donotremove #endif // The headers <libiberty.h> (binutils) and <string.h> (glibc) both declare the@@ -69,7 +68,10 @@ namespace { struct poison {}; -[[maybe_unused]] FOLLY_ERASE void rust_demangle_callback(poison);+FOLLY_PUSH_WARNING+FOLLY_GCC_DISABLE_WARNING("-Wunused-function")+[[maybe_unused]] void rust_demangle_callback(poison);+FOLLY_POP_WARNING [[maybe_unused]] FOLLY_ERASE int rust_demangle_callback_fallback( const char*, int, demangle_callbackref, void*) {@@ -125,10 +127,16 @@ namespace folly { -bool const demangle_build_has_cxxabi = cxxabi_demangle;-bool const demangle_build_has_liberty = //- to_bool(liberty_cplus_demangle) && //- to_bool(liberty_rust_demangle);+bool demangle_build_has_cxxabi() noexcept {+ return to_bool(cxxabi_demangle);+}+bool demangle_build_has_liberty() noexcept {+ bool vals[] = {+ to_bool(liberty_cplus_demangle),+ to_bool(liberty_rust_demangle),+ };+ return std::all_of(std::begin(vals), std::end(vals), folly::identity);+} namespace { void demangleStringCallback(const char* str, size_t size, void* p) {@@ -155,7 +163,7 @@ } } - if (folly::demangle_build_has_liberty) {+ if (folly::demangle_build_has_liberty()) { liberty_demangle_t* funcs[] = { liberty_rust_demangle, liberty_cplus_demangle,@@ -223,7 +231,7 @@ } } - if (folly::demangle_build_has_liberty) {+ if (folly::demangle_build_has_liberty()) { liberty_demangle_t* funcs[] = { liberty_rust_demangle, liberty_cplus_demangle,
@@ -28,8 +28,8 @@ 0; #endif -extern bool const demangle_build_has_cxxabi;-extern bool const demangle_build_has_liberty;+bool demangle_build_has_cxxabi() noexcept;+bool demangle_build_has_liberty() noexcept; /** * Return the demangled (prettified) version of a C++ type.
@@ -14,4 +14,4 @@ * limitations under the License. */ -#include <folly/json/DynamicConverter.h>+#include <folly/json/DynamicConverter.h> // @shim
@@ -73,9 +73,10 @@ template < class Ex, class Ex_,- FOLLY_REQUIRES_DEF(Conjunction<- exception_wrapper::IsStdException<Ex_>,- exception_wrapper::IsRegularExceptionType<Ex_>>::value)>+ FOLLY_REQUIRES_DEF(+ Conjunction<+ exception_wrapper::IsStdException<Ex_>,+ exception_wrapper::IsRegularExceptionType<Ex_>>::value)> inline exception_wrapper::exception_wrapper(Ex&& ex) : ptr_{make_exception_ptr_with(std::in_place, std::forward<Ex>(ex))} {} @@ -131,7 +132,8 @@ return !!ptr_; } -inline std::exception* exception_wrapper::get_exception() noexcept {+inline std::exception* exception_wrapper::get_mutable_exception()+ const noexcept { return exception_ptr_get_object<std::exception>(ptr_); } inline std::exception const* exception_wrapper::get_exception() const noexcept {@@ -139,7 +141,7 @@ } template <typename Ex>-inline Ex* exception_wrapper::get_exception() noexcept {+inline Ex* exception_wrapper::get_mutable_exception() const noexcept { return exception_ptr_get_object_hint<Ex>(ptr_); } @@ -209,8 +211,13 @@ template <class This, class Fn, typename Ex> inline bool exception_wrapper::with_exception_(This& this_, Fn fn_, tag_t<Ex>) {- auto ptr = this_.template get_exception<remove_cvref_t<Ex>>();- return ptr && (void(fn_(static_cast<Ex&>(*ptr))), true);+ if constexpr (std::is_const_v<Ex>) {+ auto ptr = this_.template get_exception<remove_cvref_t<Ex>>();+ return ptr && (void(fn_(static_cast<Ex&>(*ptr))), true);+ } else {+ auto ptr = this_.template get_mutable_exception<remove_cvref_t<Ex>>();+ return ptr && (void(fn_(static_cast<Ex&>(*ptr))), true);+ } } template <class Ex, class This, class Fn>
@@ -25,6 +25,7 @@ #include <type_traits> #include <typeinfo> #include <utility>+#include <fmt/format.h> #include <folly/CPortability.h> #include <folly/CppAttributes.h>@@ -204,8 +205,7 @@ //! \return a pointer to the `std::exception` held by `*this`, if it holds //! one; otherwise, returns `nullptr`.- std::exception* get_exception() noexcept;- //! \overload+ std::exception* get_mutable_exception() const noexcept; std::exception const* get_exception() const noexcept; //! \returns a pointer to the `Ex` held by `*this`, if it holds an object@@ -217,10 +217,10 @@ //! //! This is most efficient when `Ex` matches the exact stored type, or when //! the type alias `Ex::folly_get_exception_hint_types` has a good hint.- template <typename Ex>- Ex* get_exception() noexcept; //! \overload template <typename Ex>+ Ex* get_mutable_exception() const noexcept;+ template <typename Ex> Ex const* get_exception() const noexcept; //! \return A `std::exception_ptr` that references the exception held by@@ -285,7 +285,7 @@ //! assert( !exception_wrapper{}.with_exception([](int& e){/*...*/}) ); //! \endcode //! \tparam Ex Optionally, the type of the exception that `fn` accepts.- //! \tparam Fn The type of a monomophic function object.+ //! \tparam Fn The type of a monomorphic function object. //! \param fn A function object to call with the wrapped exception //! \return `true` if and only if `fn` was called. //! \note Optionally, you may explicitly specify the type of the exception@@ -350,8 +350,8 @@ return get_exception<Ex>(); } template <typename Ex>- Ex* get_mutable_exception(get_exception_tag_t) noexcept {- return get_exception<Ex>();+ Ex* get_mutable_exception(get_exception_tag_t) const noexcept {+ return get_mutable_exception<Ex>(); } }; @@ -395,7 +395,23 @@ auto x = [&] { return void(static_cast<F&&>(fn)()), std::exception_ptr{}; }; return exception_wrapper{catch_exception(x, current_exception)}; }++/**+ * A convenience shorthand for `exception_wrapper(current_exception())`.+ */+inline exception_wrapper current_exception_wrapper() noexcept {+ return exception_wrapper{current_exception()};+}+ } // namespace folly++template <>+struct fmt::formatter<folly::exception_wrapper> : formatter<folly::fbstring> {+ template <typename Context>+ auto format(const folly::exception_wrapper& ew, Context& ctx) const {+ return formatter<folly::fbstring>::format(ew.what(), ctx);+ }+}; #include <folly/ExceptionWrapper-inl.h>
@@ -49,163 +49,162 @@ } // namespace detail -/// An Executor accepts units of work with add(), which should be-/// threadsafe.-class Executor {+class Executor;++/**+ * `ExecutorKeepAlive` is a safe pointer to an `Executor`.+ *+ * For any `Executor` that supports keep-alive functionality, its destructor+ * will block until all the `ExecutorKeepAlive` objects associated with that+ * executor are destroyed. For executors that don't support the keep-alive+ * functionality, `ExecutorKeepAlive` doesn't provide such protection.+ *+ * `ExecutorKeepAlive` should *always* be used instead of `Executor*`.+ * `ExecutorKeepAlive` can be implicitly constructed from `Executor*`.+ *+ * The `getKeepAliveToken()` helper can be used to construct a keep-alive in+ * templated code if you need to preserve the original executor type.+ */+template <typename ExecutorT = Executor>+class ExecutorKeepAlive : private detail::ExecutorKeepAliveBase { public:- virtual ~Executor() = default;+ using KeepAliveFunc = Function<void(ExecutorKeepAlive&&)>; - /// Enqueue a function to be executed by this executor. This and all- /// variants must be threadsafe.- virtual void add(Func) = 0;+ ExecutorKeepAlive() = default; - /// Enqueue a function with a given priority, where 0 is the medium priority- /// This is up to the implementation to enforce- virtual void addWithPriority(Func, int8_t priority);+ ~ExecutorKeepAlive() {+ static_assert(+ std::is_standard_layout<ExecutorKeepAlive>::value, "standard-layout");+ static_assert(sizeof(ExecutorKeepAlive) == sizeof(void*), "pointer size");+ static_assert(+ alignof(ExecutorKeepAlive) == alignof(void*), "pointer align"); - virtual uint8_t getNumPriorities() const { return 1; }+ reset();+ } - static constexpr int8_t LO_PRI = SCHAR_MIN;- static constexpr int8_t MID_PRI = 0;- static constexpr int8_t HI_PRI = SCHAR_MAX;+ ExecutorKeepAlive(ExecutorKeepAlive&& other) noexcept+ : storage_(std::exchange(other.storage_, 0)) {} - /**- * Executor::KeepAlive is a safe pointer to an Executor.- * For any Executor that supports KeepAlive functionality, Executor's- * destructor will block until all the KeepAlive objects associated with that- * Executor are destroyed.- * For Executors that don't support the KeepAlive functionality, KeepAlive- * doesn't provide such protection.- *- * KeepAlive should *always* be used instead of Executor*. KeepAlive can be- * implicitly constructed from Executor*. getKeepAliveToken() helper method- * can be used to construct a KeepAlive in templated code if you need to- * preserve the original Executor type.- */- template <typename ExecutorT = Executor>- class KeepAlive : private detail::ExecutorKeepAliveBase {- public:- using KeepAliveFunc = Function<void(KeepAlive&&)>;+ ExecutorKeepAlive(const ExecutorKeepAlive& other) noexcept; - KeepAlive() = default;+ template <+ typename OtherExecutor,+ typename = typename std::enable_if<+ std::is_convertible<OtherExecutor*, ExecutorT*>::value>::type>+ /* implicit */ ExecutorKeepAlive(+ ExecutorKeepAlive<OtherExecutor>&& other) noexcept+ : ExecutorKeepAlive(other.get(), other.storage_ & kFlagMask) {+ other.storage_ = 0;+ } - ~KeepAlive() {- static_assert(- std::is_standard_layout<KeepAlive>::value, "standard-layout");- static_assert(sizeof(KeepAlive) == sizeof(void*), "pointer size");- static_assert(alignof(KeepAlive) == alignof(void*), "pointer align");+ template <+ typename OtherExecutor,+ typename = typename std::enable_if<+ std::is_convertible<OtherExecutor*, ExecutorT*>::value>::type>+ /* implicit */ ExecutorKeepAlive(+ const ExecutorKeepAlive<OtherExecutor>& other) noexcept; - reset();- }+ /* implicit */ ExecutorKeepAlive(ExecutorT* executor); - KeepAlive(KeepAlive&& other) noexcept- : storage_(std::exchange(other.storage_, 0)) {}+ ExecutorKeepAlive& operator=(ExecutorKeepAlive&& other) noexcept {+ reset();+ storage_ = std::exchange(other.storage_, 0);+ return *this;+ } - KeepAlive(const KeepAlive& other) noexcept- : KeepAlive(getKeepAliveToken(other.get())) {}+ ExecutorKeepAlive& operator=(ExecutorKeepAlive const& other) {+ return operator=(folly::copy(other));+ } - template <- typename OtherExecutor,- typename = typename std::enable_if<- std::is_convertible<OtherExecutor*, ExecutorT*>::value>::type>- /* implicit */ KeepAlive(KeepAlive<OtherExecutor>&& other) noexcept- : KeepAlive(other.get(), other.storage_ & kFlagMask) {- other.storage_ = 0;- }+ template <+ typename OtherExecutor,+ typename = typename std::enable_if<+ std::is_convertible<OtherExecutor*, ExecutorT*>::value>::type>+ ExecutorKeepAlive& operator=(+ ExecutorKeepAlive<OtherExecutor>&& other) noexcept {+ return *this = ExecutorKeepAlive(std::move(other));+ } - template <- typename OtherExecutor,- typename = typename std::enable_if<- std::is_convertible<OtherExecutor*, ExecutorT*>::value>::type>- /* implicit */ KeepAlive(const KeepAlive<OtherExecutor>& other) noexcept- : KeepAlive(getKeepAliveToken(other.get())) {}+ template <+ typename OtherExecutor,+ typename = typename std::enable_if<+ std::is_convertible<OtherExecutor*, ExecutorT*>::value>::type>+ ExecutorKeepAlive& operator=(const ExecutorKeepAlive<OtherExecutor>& other) {+ return *this = ExecutorKeepAlive(other);+ } - /* implicit */ KeepAlive(ExecutorT* executor) {- *this = getKeepAliveToken(executor);- }+ void reset() noexcept; - KeepAlive& operator=(KeepAlive&& other) noexcept {- reset();- storage_ = std::exchange(other.storage_, 0);- return *this;- }+ explicit operator bool() const { return storage_; } - KeepAlive& operator=(KeepAlive const& other) {- return operator=(folly::copy(other));- }+ ExecutorT* get() const {+ return reinterpret_cast<ExecutorT*>(storage_ & kExecutorMask);+ } - template <- typename OtherExecutor,- typename = typename std::enable_if<- std::is_convertible<OtherExecutor*, ExecutorT*>::value>::type>- KeepAlive& operator=(KeepAlive<OtherExecutor>&& other) noexcept {- return *this = KeepAlive(std::move(other));- }+ ExecutorT& operator*() const { return *get(); } - template <- typename OtherExecutor,- typename = typename std::enable_if<- std::is_convertible<OtherExecutor*, ExecutorT*>::value>::type>- KeepAlive& operator=(const KeepAlive<OtherExecutor>& other) {- return *this = KeepAlive(other);- }+ ExecutorT* operator->() const { return get(); } - void reset() noexcept {- if (Executor* executor = get()) {- auto const flags = std::exchange(storage_, 0) & kFlagMask;- if (!(flags & (kDummyFlag | kAliasFlag))) {- executor->keepAliveRelease();- }- }- }+ ExecutorKeepAlive copy() const; - explicit operator bool() const { return storage_; }+ ExecutorKeepAlive get_alias() const {+ return ExecutorKeepAlive(storage_ | kAliasFlag);+ } - ExecutorT* get() const {- return reinterpret_cast<ExecutorT*>(storage_ & kExecutorMask);- }+ template <class KAF>+ void add(KAF&& f) && {+ static_assert(+ is_invocable<KAF, ExecutorKeepAlive&&>::value,+ "Parameter to add must be void(ExecutorKeepAlive&&)>");+ auto ex = get();+ ex->add([ka = std::move(*this), f_2 = std::forward<KAF>(f)]() mutable {+ f_2(std::move(ka));+ });+ } - ExecutorT& operator*() const { return *get(); }+ private:+ friend class Executor;+ template <typename OtherExecutor>+ friend class ExecutorKeepAlive; - ExecutorT* operator->() const { return get(); }+ ExecutorKeepAlive(ExecutorT* executor, uintptr_t flags) noexcept+ : storage_(reinterpret_cast<uintptr_t>(executor) | flags) {+ assert(executor);+ assert(!(reinterpret_cast<uintptr_t>(executor) & ~kExecutorMask));+ assert(!(flags & kExecutorMask));+ } - KeepAlive copy() const {- return isKeepAliveDummy(*this) //- ? makeKeepAliveDummy(get())- : getKeepAliveToken(get());- }+ explicit ExecutorKeepAlive(uintptr_t storage) noexcept : storage_(storage) {} - KeepAlive get_alias() const { return KeepAlive(storage_ | kAliasFlag); }+ // Combined storage for the executor pointer and for all flags.+ uintptr_t storage_{reinterpret_cast<uintptr_t>(nullptr)};+}; - template <class KAF>- void add(KAF&& f) && {- static_assert(- is_invocable<KAF, KeepAlive&&>::value,- "Parameter to add must be void(KeepAlive&&)>");- auto ex = get();- ex->add([ka = std::move(*this), f_2 = std::forward<KAF>(f)]() mutable {- f_2(std::move(ka));- });- }+/// An Executor accepts units of work with add(), which should be+/// threadsafe.+class Executor {+ public:+ virtual ~Executor() = default; - private:- friend class Executor;- template <typename OtherExecutor>- friend class KeepAlive;+ /// Enqueue a function to be executed by this executor. This and all+ /// variants must be threadsafe.+ virtual void add(Func) = 0; - KeepAlive(ExecutorT* executor, uintptr_t flags) noexcept- : storage_(reinterpret_cast<uintptr_t>(executor) | flags) {- assert(executor);- assert(!(reinterpret_cast<uintptr_t>(executor) & ~kExecutorMask));- assert(!(flags & kExecutorMask));- }+ /// Enqueue a function with a given priority, where 0 is the medium priority+ /// This is up to the implementation to enforce+ virtual void addWithPriority(Func, int8_t priority); - explicit KeepAlive(uintptr_t storage) noexcept : storage_(storage) {}+ virtual uint8_t getNumPriorities() const { return 1; } - // Combined storage for the executor pointer and for all flags.- uintptr_t storage_{reinterpret_cast<uintptr_t>(nullptr)};- };+ static constexpr int8_t LO_PRI = SCHAR_MIN;+ static constexpr int8_t MID_PRI = 0;+ static constexpr int8_t HI_PRI = SCHAR_MAX; + // Compatibility shim. Cannot be forward-declared, unlike+ // `ExecutorKeepAlive`.+ template <typename ExecutorT = Executor>+ using KeepAlive = ExecutorKeepAlive<ExecutorT>;+ template <typename ExecutorT> static KeepAlive<ExecutorT> getKeepAliveToken(ExecutorT* executor) { static_assert(@@ -235,6 +234,9 @@ } protected:+ template <typename>+ friend class ExecutorKeepAlive;+ /** * Returns true if the KeepAlive is constructed from an executor that does * not support the keep alive ref-counting functionality@@ -277,6 +279,39 @@ return KeepAlive<ExecutorT>{executor, KeepAlive<ExecutorT>::kDummyFlag}; } };++template <typename ExecutorT>+ExecutorKeepAlive<ExecutorT>::ExecutorKeepAlive(+ const ExecutorKeepAlive<ExecutorT>& other) noexcept+ : ExecutorKeepAlive(Executor::getKeepAliveToken(other.get())) {}++template <typename ExecutorT>+template <typename OtherExecutor, typename>+ExecutorKeepAlive<ExecutorT>::ExecutorKeepAlive(+ const ExecutorKeepAlive<OtherExecutor>& other) noexcept+ : ExecutorKeepAlive(Executor::getKeepAliveToken(other.get())) {}++template <typename ExecutorT>+ExecutorKeepAlive<ExecutorT>::ExecutorKeepAlive(ExecutorT* executor) {+ *this = Executor::getKeepAliveToken(executor);+}++template <typename ExecutorT>+void ExecutorKeepAlive<ExecutorT>::reset() noexcept {+ if (Executor* executor = get()) {+ auto const flags = std::exchange(storage_, 0) & kFlagMask;+ if (!(flags & (kDummyFlag | kAliasFlag))) {+ executor->keepAliveRelease();+ }+ }+}++template <typename ExecutorT>+ExecutorKeepAlive<ExecutorT> ExecutorKeepAlive<ExecutorT>::copy() const {+ return Executor::isKeepAliveDummy(*this) //+ ? Executor::makeKeepAliveDummy(get())+ : Executor::getKeepAliveToken(get());+} /// Returns a keep-alive token which guarantees that Executor will keep /// processing tasks until the token is released (if supported by Executor).
@@ -65,7 +65,7 @@ * Expected objects in the error state. */ template <class Error>-class FOLLY_NODISCARD Unexpected final {+class [[nodiscard]] Unexpected final { template <class E> friend class Unexpected; template <class V, class E>@@ -103,10 +103,10 @@ /** * Observers */- Error& error() & { return error_; }- const Error& error() const& { return error_; }- Error&& error() && { return std::move(error_); }- const Error&& error() const&& { return std::move(error_); }+ Error& error() & noexcept { return error_; }+ const Error& error() const& noexcept { return error_; }+ Error&& error() && noexcept { return std::move(error_); }+ const Error&& error() const&& noexcept { return std::move(error_); } private: Error error_;@@ -193,7 +193,7 @@ class Expected; template <class Error, class Value>-FOLLY_NODISCARD constexpr Expected<typename std::decay<Value>::type, Error>+[[nodiscard]] constexpr Expected<typename std::decay<Value>::type, Error> makeExpected(Value&&); /**@@ -512,6 +512,7 @@ this->error().~Error(); break; case Which::eEmpty:+ default: break; } this->which_ = Which::eEmpty;@@ -672,11 +673,10 @@ class... Fns, class E = ExpectedErrorType<This>, class T = ExpectedHelper>- static auto then_(This&& ex, Fn&& fn, Fns&&... fns)- -> decltype(T::then_(- T::template return_<E>(- (std::declval<Fn>()(std::declval<This>().value()), unit)),- std::declval<Fns>()...)) {+ static auto then_(This&& ex, Fn&& fn, Fns&&... fns) -> decltype(T::then_(+ T::template return_<E>(+ (std::declval<Fn>()(std::declval<This>().value()), unit)),+ std::declval<Fns>()...)) { if (FOLLY_LIKELY(ex.which_ == expected_detail::Which::eValue)) { return T::then_( T::template return_<E>(@@ -745,10 +745,11 @@ // Note - this basically decays into then_ once the first type (No) is // called for the error. if (FOLLY_LIKELY(ex.which_ == expected_detail::Which::eValue)) {- return T::template return_<E>((T::then_(T::template return_<E>(- // Uses the comma operator defined above IFF the lambda- // returns non-void.- static_cast<decltype(ex)&&>(ex).value()))));+ return T::template return_<E>((T::then_(+ T::template return_<E>(+ // Uses the comma operator defined above IFF the lambda+ // returns non-void.+ static_cast<decltype(ex)&&>(ex).value())))); } return T::then_( T::template return_<E>(@@ -887,7 +888,7 @@ * .then([](C c){return D(c);}); * * To avoid the redundant error checking that would happen if a call at the- * front of the chain returns an error, these call chains can be collaped into+ * front of the chain returns an error, these call chains can be collapsed into * a single call to .then: * * Expected<D, Error> maybeD = someFunc()@@ -933,7 +934,7 @@ * to-Error or vice versa) throws. Trying to access either the contained value * or error object causes Expected to throw folly::BadExpectedAccess. *- * Expected models OptionalPointee, so calling 'get_pointer(ex)' will return a+ * Expected models OptionalPointer, so calling 'get_pointer(ex)' will return a * pointer to nullptr if the 'ex' is in the error state, and a pointer to the * value otherwise: *@@ -1265,9 +1266,9 @@ * then */ template <class... Fns FOLLY_REQUIRES_TRAILING(sizeof...(Fns) >= 1)>- auto then(Fns&&... fns)- const& -> decltype(expected_detail::ExpectedHelper::then_(- std::declval<const Base&>(), std::declval<Fns>()...)) {+ auto+ then(Fns&&... fns) const& -> decltype(expected_detail::ExpectedHelper::then_(+ std::declval<const Base&>(), std::declval<Fns>()...)) { if (this->uninitializedByException()) { throw_exception<BadExpectedAccess<void>>(); }@@ -1276,9 +1277,8 @@ } template <class... Fns FOLLY_REQUIRES_TRAILING(sizeof...(Fns) >= 1)>- auto- then(Fns&&... fns) & -> decltype(expected_detail::ExpectedHelper::then_(- std::declval<Base&>(), std::declval<Fns>()...)) {+ auto then(Fns&&... fns) & -> decltype(expected_detail::ExpectedHelper::then_(+ std::declval<Base&>(), std::declval<Fns>()...)) { if (this->uninitializedByException()) { throw_exception<BadExpectedAccess<void>>(); }@@ -1287,9 +1287,8 @@ } template <class... Fns FOLLY_REQUIRES_TRAILING(sizeof...(Fns) >= 1)>- auto- then(Fns&&... fns) && -> decltype(expected_detail::ExpectedHelper::then_(- std::declval<Base&&>(), std::declval<Fns>()...)) {+ auto then(Fns&&... fns) && -> decltype(expected_detail::ExpectedHelper::then_(+ std::declval<Base&&>(), std::declval<Fns>()...)) { if (this->uninitializedByException()) { throw_exception<BadExpectedAccess<void>>(); }@@ -1304,7 +1303,7 @@ template <class... Fns FOLLY_REQUIRES_TRAILING(sizeof...(Fns) >= 1)> auto orElse(Fns&&... fns) const& -> decltype(expected_detail::ExpectedHelper::orElse_(- std::declval<const Base&>(), std::declval<Fns>()...)) {+ std::declval<const Base&>(), std::declval<Fns>()...)) { if (this->uninitializedByException()) { throw_exception<BadExpectedAccess<void>>(); }@@ -1315,7 +1314,7 @@ template <class... Fns FOLLY_REQUIRES_TRAILING(sizeof...(Fns) >= 1)> auto orElse(Fns&&... fns) & -> decltype(expected_detail::ExpectedHelper::orElse_(- std::declval<Base&>(), std::declval<Fns>()...)) {+ std::declval<Base&>(), std::declval<Fns>()...)) { if (this->uninitializedByException()) { throw_exception<BadExpectedAccess<void>>(); }@@ -1326,7 +1325,7 @@ template <class... Fns FOLLY_REQUIRES_TRAILING(sizeof...(Fns) >= 1)> auto orElse(Fns&&... fns) && -> decltype(expected_detail::ExpectedHelper::orElse_(- std::declval<Base&&>(), std::declval<Fns>()...)) {+ std::declval<Base&&>(), std::declval<Fns>()...)) { if (this->uninitializedByException()) { throw_exception<BadExpectedAccess<void>>(); }@@ -1344,34 +1343,34 @@ if (this->uninitializedByException()) { throw_exception<BadExpectedAccess<void>>(); }- return Ret(expected_detail::ExpectedHelper::thenOrThrow_(- base(), static_cast<Yes&&>(yes), static_cast<No&&>(no)));+ return Ret(+ expected_detail::ExpectedHelper::thenOrThrow_(+ base(), static_cast<Yes&&>(yes), static_cast<No&&>(no))); } template <class Yes, class No = MakeBadExpectedAccess>- auto thenOrThrow(- Yes&& yes,- No&& no =- No{}) & -> decltype(std::declval<Yes>()(std::declval<Value&>())) {+ auto thenOrThrow(Yes&& yes, No&& no = No{}) & -> decltype(std::declval<Yes>()(+ std::declval<Value&>())) { using Ret = decltype(std::declval<Yes>()(std::declval<Value&>())); if (this->uninitializedByException()) { throw_exception<BadExpectedAccess<void>>(); }- return Ret(expected_detail::ExpectedHelper::thenOrThrow_(- base(), static_cast<Yes&&>(yes), static_cast<No&&>(no)));+ return Ret(+ expected_detail::ExpectedHelper::thenOrThrow_(+ base(), static_cast<Yes&&>(yes), static_cast<No&&>(no))); } template <class Yes, class No = MakeBadExpectedAccess>- auto thenOrThrow(- Yes&& yes,- No&& no =- No{}) && -> decltype(std::declval<Yes>()(std::declval<Value&&>())) {+ auto+ thenOrThrow(Yes&& yes, No&& no = No{}) && -> decltype(std::declval<Yes>()(+ std::declval<Value&&>())) { using Ret = decltype(std::declval<Yes>()(std::declval<Value&&>())); if (this->uninitializedByException()) { throw_exception<BadExpectedAccess<void>>(); }- return Ret(expected_detail::ExpectedHelper::thenOrThrow_(- std::move(base()), static_cast<Yes&&>(yes), static_cast<No&&>(no)));+ return Ret(+ expected_detail::ExpectedHelper::thenOrThrow_(+ std::move(base()), static_cast<Yes&&>(yes), static_cast<No&&>(no))); } /**@@ -1551,7 +1550,7 @@ * } */ template <class Error, class Value>-FOLLY_NODISCARD constexpr Expected<typename std::decay<Value>::type, Error>+[[nodiscard]] constexpr Expected<typename std::decay<Value>::type, Error> makeExpected(Value&& val) { return Expected<typename std::decay<Value>::type, Error>{ std::in_place, static_cast<Value&&>(val)};@@ -1590,6 +1589,8 @@ // Enable the use of folly::Expected with `co_await` // Inspired by https://github.com/toby-allsopp/coroutine_monad+//+// NOTE: Please port applicable improvements to `folly/result/coro.h`. #if FOLLY_HAS_COROUTINES #include <folly/coro/Coroutine.h> @@ -1632,12 +1633,8 @@ PromiseBase(PromiseBase const&) = delete; void operator=(PromiseBase const&) = delete; - [[nodiscard]] coro::suspend_never initial_suspend() const noexcept {- return {};- }- [[nodiscard]] coro::suspend_never final_suspend() const noexcept {- return {};- }+ coro::suspend_never initial_suspend() const noexcept { return {}; }+ coro::suspend_never final_suspend() const noexcept { return {}; } [[noreturn]] void unhandled_exception() { // Technically, throwing from unhandled_exception is underspecified: // https://github.com/GorNishanov/CoroutineWording/issues/17@@ -1684,8 +1681,9 @@ void await_resume() { compiler_may_unsafely_assume_unreachable(); } template <typename U>- FOLLY_ALWAYS_INLINE void await_suspend(- coro::coroutine_handle<Promise<U, Error>> h) {+ FOLLY_ALWAYS_INLINE void+ await_suspend(coro::coroutine_handle<Promise<U, Error>> h) noexcept(+ IsNothrowMovable<Error>::value) { auto& v = *h.promise().value_; ExpectedHelper::assume_empty(v); v = std::move(o_);@@ -1704,8 +1702,9 @@ // Explicitly only allow suspension into a Promise template <typename U>- FOLLY_ALWAYS_INLINE void await_suspend(- coro::coroutine_handle<Promise<U, Error>> h) {+ FOLLY_ALWAYS_INLINE void+ await_suspend(coro::coroutine_handle<Promise<U, Error>> h) noexcept(+ IsNothrowMovable<Error>::value) { auto& v = *h.promise().value_; ExpectedHelper::assume_empty(v); v = makeUnexpected(std::move(o_.error()));
@@ -205,7 +205,7 @@ /** * This is the core of the string. The code should work on 32- and- * 64-bit and both big- and little-endianan architectures with any+ * 64-bit and both big- and little-endian architectures with any * Char size. * * The storage is selected as follows (assuming we store one-byte@@ -344,8 +344,9 @@ return ml_.data_; case Category::isLarge: return mutableDataLarge();+ default:+ folly::assume_unreachable(); }- folly::assume_unreachable(); } const Char* c_str() const {@@ -437,6 +438,20 @@ return category() == Category::isLarge && RefCounted::refs(ml_.data_) > 1; } + // Returns the reference count for Large (RefCounted) strings, or 1 for+ // Small/Medium strings which are not reference counted.+ size_t useCount() const {+ if (category() == Category::isLarge) {+ return RefCounted::refs(ml_.data_);+ }+ return 1;+ }++ // Returns true if the string is using RefCounted (Large) storage mode.+ // Large strings have a RefCounted header that adds sizeof(size_t) bytes+ // to the allocation, which is important for accurate memory accounting.+ bool isCounted() const { return category() == Category::isLarge; }+ private: Char* c_str() { Char* ptr = ml_.data_;@@ -927,6 +942,8 @@ size_t size() const { return backend_.size(); } size_t capacity() const { return backend_.capacity(); } bool isShared() const { return false; }+ size_t useCount() const { return 1; }+ bool isCounted() const { return false; } void reserve(size_t minCapacity) { backend_.reserve(minCapacity); } private:@@ -1220,6 +1237,17 @@ void resize(size_type n, value_type c = value_type()); size_type capacity() const { return store_.capacity(); }++ // Returns the reference count for this string's underlying data.+ // Returns 1 for Small (SSO) or Medium strings which are not reference+ // counted. For Large strings, returns the actual reference count.+ // Useful for memory accounting with fair-share division.+ size_t use_count() const { return store_.useCount(); }++ // Returns true if the string uses RefCounted (Large) storage mode.+ // Large strings have a RefCounted header (sizeof(size_t) bytes) before+ // the data, which must be accounted for in memory calculations.+ bool is_counted() const { return store_.isCounted(); } void reserve(size_type res_arg = 0) { enforce<std::length_error>(res_arg <= max_size(), "");
@@ -14,4 +14,4 @@ * limitations under the License. */ -#include <folly/container/FBVector.h>+#include <folly/container/FBVector.h> // @shim
@@ -38,11 +38,12 @@ File::File(const char* name, int flags, mode_t mode) : fd_(fileops::open(name, flags, mode)), ownsFd_(false) { if (fd_ == -1) {- throwSystemError(fmt::format(- FOLLY_FMT_COMPILE("open(\"{}\", {:#o}, 0{:#o}) failed"),- name,- flags,- mode));+ throwSystemError(+ fmt::format(+ FOLLY_FMT_COMPILE("open(\"{}\", {:#o}, 0{:#o}) failed"),+ name,+ flags,+ mode)); } ownsFd_ = true; }
@@ -53,7 +53,7 @@ // https://github.com/llvm/llvm-project/commit/0a0e411204a2baa520fd73a8d69b664f98b428ba // auto openWrapper = [&] { return fileops::open(name, flags, mode); };- return int(wrapNoInt(openWrapper));+ return wrapNoInt(openWrapper); } static int filterCloseReturn(int r) {@@ -81,41 +81,41 @@ } int fsyncNoInt(int fd) {- return int(wrapNoInt(fsync, fd));+ return wrapNoInt(fsync, fd); } int dupNoInt(int fd) {- return int(wrapNoInt(dup, fd));+ return wrapNoInt(dup, fd); } int dup2NoInt(int oldFd, int newFd) {- return int(wrapNoInt(dup2, oldFd, newFd));+ return wrapNoInt(dup2, oldFd, newFd); } int fdatasyncNoInt(int fd) { #if defined(__APPLE__)- return int(wrapNoInt(fcntl, fd, F_FULLFSYNC));+ return wrapNoInt(fcntl, fd, F_FULLFSYNC); #elif defined(__FreeBSD__) || defined(_MSC_VER)- return int(wrapNoInt(fsync, fd));+ return wrapNoInt(fsync, fd); #else- return int(wrapNoInt(fdatasync, fd));+ return wrapNoInt(fdatasync, fd); #endif } int ftruncateNoInt(int fd, off_t len) {- return int(wrapNoInt(ftruncate, fd, len));+ return wrapNoInt(ftruncate, fd, len); } int truncateNoInt(const char* path, off_t len) {- return int(wrapNoInt(truncate, path, len));+ return wrapNoInt(truncate, path, len); } int flockNoInt(int fd, int operation) {- return int(wrapNoInt(flock, fd, operation));+ return wrapNoInt(flock, fd, operation); } int shutdownNoInt(NetworkSocket fd, int how) {- return int(wrapNoInt(netops::shutdown, fd, how));+ return wrapNoInt(netops::shutdown, fd, how); } ssize_t readNoInt(int fd, void* buf, size_t count) {
@@ -83,11 +83,10 @@ * readv and preadv. The contents of iov after these functions return * is unspecified. */-FOLLY_NODISCARD ssize_t readFull(int fd, void* buf, size_t count);-FOLLY_NODISCARD ssize_t-preadFull(int fd, void* buf, size_t count, off_t offset);-FOLLY_NODISCARD ssize_t readvFull(int fd, iovec* iov, int count);-FOLLY_NODISCARD ssize_t preadvFull(int fd, iovec* iov, int count, off_t offset);+[[nodiscard]] ssize_t readFull(int fd, void* buf, size_t count);+[[nodiscard]] ssize_t preadFull(int fd, void* buf, size_t count, off_t offset);+[[nodiscard]] ssize_t readvFull(int fd, iovec* iov, int count);+[[nodiscard]] ssize_t preadvFull(int fd, iovec* iov, int count, off_t offset); /** * Similar to readFull and preadFull above, wrappers around write() and@@ -147,8 +146,10 @@ // should attempt to read stuff. If not zero, we'll attempt to read // one extra byte. constexpr size_t initialAlloc = 1024 * 4;- out.resize(std::min(- buf.st_size > 0 ? (size_t(buf.st_size) + 1) : initialAlloc, num_bytes));+ out.resize(+ std::min(+ buf.st_size > 0 ? (size_t(buf.st_size) + 1) : initialAlloc,+ num_bytes)); while (soFar < out.size()) { const auto actual = readFull(fd, &out[soFar], out.size() - soFar);
@@ -17,7 +17,6 @@ #include <folly/Fingerprint.h> #include <folly/Portability.h>-#include <folly/Utility.h> #include <folly/detail/FingerprintPolynomial.h> #include <utility>
@@ -14,7 +14,7 @@ * limitations under the License. */ -#include <cstring>+#include <cstring> // @donotremove #if !defined(__AVX2__) && !(defined(__linux__) && defined(__aarch64__)) namespace folly {
@@ -14,7 +14,7 @@ * limitations under the License. */ -#include <cstring>+#include <cstring> // @donotremove #if !defined(__AVX2__) && !(defined(__linux__) && defined(__aarch64__))
@@ -19,11 +19,10 @@ #include <cassert> #include <folly/ConstexprMath.h>-#include <folly/CppAttributes.h> #include <folly/Portability.h> #include <folly/container/Array.h> -#include <double-conversion/double-conversion.h>+#include <double-conversion/double-conversion.h> // @donotremove namespace folly { namespace detail {
@@ -57,8 +57,6 @@ class Formatter; template <class... Args> Formatter<false, Args...> format(StringPiece fmt, Args&&... args);-template <class C>-std::string svformat(StringPiece fmt, C&& container) = delete; template <class T, class Enable = void> class FormatValue; @@ -158,7 +156,7 @@ /** * Metadata to identify generated children of BaseFormatter */- typedef detail::FormatterTag IsFormatter;+ using IsFormatter = detail::FormatterTag; private: template <typename T, typename D = typename std::decay<T>::type>@@ -249,8 +247,6 @@ Str* out, StringPiece fmt, A&&... args); template <class... A> friend std::string sformat(StringPiece fmt, A&&... arg);- template <class C>- friend std::string svformat(StringPiece fmt, C&& container); }; namespace detail {@@ -287,8 +283,7 @@ [[deprecated( "Use fmt::format instead of folly::format for better performance, build " "times and compatibility with std::format")]] //-Formatter<false, Args...>-format(StringPiece fmt, Args&&... args) {+Formatter<false, Args...> format(StringPiece fmt, Args&&... args) { return Formatter<false, Args...>(fmt, static_cast<Args&&>(args)...); }
@@ -24,7 +24,7 @@ // Shortcut, so we don't have to use enable_if everywhere struct FormatTraitsBase {- typedef void enabled;+ using enabled = void; }; // Traits that define enabled, value_type, and at() for anything@@ -36,8 +36,8 @@ // Base class for sequences (vectors, deques) template <class C> struct IndexableTraitsSeq : public FormatTraitsBase {- typedef C container_type;- typedef typename C::value_type value_type;+ using container_type = C;+ using value_type = typename C::value_type; static const value_type& at(const C& c, int idx) { return c.at(idx); } @@ -49,7 +49,7 @@ // Base class for associative types (maps) template <class C> struct IndexableTraitsAssoc : public FormatTraitsBase {- typedef typename C::value_type::second_type value_type;+ using value_type = typename C::value_type::second_type; static const value_type& at(const C& c, int idx) { return c.at(static_cast<typename C::key_type>(idx));
@@ -558,8 +558,9 @@ static std::size_t exec(Op o, Data* src, Data* dst) noexcept { switch (o) { case Op::MOVE:- ::new (static_cast<void*>(&dst->tiny)) Fun(static_cast<Fun&&>(- *static_cast<Fun*>(static_cast<void*>(&src->tiny))));+ ::new (static_cast<void*>(&dst->tiny))+ Fun(static_cast<Fun&&>(+ *static_cast<Fun*>(static_cast<void*>(&src->tiny)))); [[fallthrough]]; case Op::NUKE: static_cast<Fun*>(static_cast<void*>(&src->tiny))->~Fun();@@ -616,8 +617,8 @@ InSituSize && InSituAlign && InSituNoexcept, std::is_trivially_copyable_v<Fun>>; -// This cannot be done inseide `Function` class, because the word-// `Function` there refers to the instantion and not the template.+// This cannot be done inside `Function` class, because the word+// `Function` there refers to the instantiation and not the template. template <typename T> constexpr bool is_instantiation_of_folly_function_v = is_instantiation_of_v<Function, T>;@@ -788,7 +789,7 @@ // Prevent this overload from being selected when `ptr` is not a // compatible member function pointer. typename = decltype(Function(std::mem_fn((Member Class::*)0)))>- /* implicit */ Function(Member Class::*ptr) noexcept {+ /* implicit */ Function(Member Class::* ptr) noexcept { if (ptr) { *this = std::mem_fn(ptr); }@@ -876,7 +877,7 @@ * operator is equivalent to `*this = std::mem_fn(ptr)`. */ template <typename Member, typename Class>- auto operator=(Member Class::*ptr) noexcept+ auto operator=(Member Class::* ptr) noexcept // Prevent this overload from being selected when `ptr` is not a // compatible member function pointer. -> decltype(operator=(std::mem_fn(ptr))) {
@@ -442,8 +442,8 @@ static const uint64_t kMask[]; }; -typedef GroupVarint<uint32_t> GroupVarint32;-typedef GroupVarint<uint64_t> GroupVarint64;+using GroupVarint32 = GroupVarint<uint32_t>;+using GroupVarint64 = GroupVarint<uint64_t>; /** * Simplify use of GroupVarint* for the case where data is available one@@ -456,8 +456,8 @@ template <class T, class Output> class GroupVarintEncoder { public:- typedef GroupVarint<T> Base;- typedef T type;+ using Base = GroupVarint<T>;+ using type = T; explicit GroupVarintEncoder(Output out) : out_(out), count_(0) {} @@ -521,8 +521,8 @@ template <typename T> class GroupVarintDecoder { public:- typedef GroupVarint<T> Base;- typedef T type;+ using Base = GroupVarint<T>;+ using type = T; GroupVarintDecoder() = default; @@ -622,8 +622,8 @@ size_t remaining_; }; -typedef GroupVarintDecoder<uint32_t> GroupVarint32Decoder;-typedef GroupVarintDecoder<uint64_t> GroupVarint64Decoder;+using GroupVarint32Decoder = GroupVarintDecoder<uint32_t>;+using GroupVarint64Decoder = GroupVarintDecoder<uint64_t>; } // namespace folly
@@ -17,4 +17,4 @@ #pragma once // shims:-#include <folly/hash/Hash.h>+#include <folly/hash/Hash.h> // @shim
@@ -97,9 +97,10 @@ } if (ret.error() == CIDRNetworkError::INVALID_IP_SLASH_CIDR) {- throw IPAddressFormatException(fmt::format(- "Invalid ipSlashCidr specified. Expected IP/CIDR format, got '{}'",- ipSlashCidr));+ throw IPAddressFormatException(+ fmt::format(+ "Invalid ipSlashCidr specified. Expected IP/CIDR format, got '{}'",+ ipSlashCidr)); } // Handler the remaining error cases. We re-parse the ip/mask pair@@ -120,10 +121,11 @@ auto cidr = static_cast<uint8_t>( (defaultCidr > -1) ? defaultCidr : (subnet.isV4() ? 32 : 128)); - throw IPAddressFormatException(fmt::format(- "CIDR value '{}' is > network bit count '{}'",- vec.size() == 2 ? vec.at(1) : to<string>(cidr),- subnet.bitCount()));+ throw IPAddressFormatException(+ fmt::format(+ "CIDR value '{}' is > network bit count '{}'",+ vec.size() == 2 ? vec.at(1) : to<string>(cidr),+ subnet.bitCount())); } case CIDRNetworkError::INVALID_DEFAULT_CIDR: case CIDRNetworkError::INVALID_IP_SLASH_CIDR:@@ -376,10 +378,11 @@ uint8_t IPAddress::getNthMSByte(size_t byteIndex) const { const auto highestIndex = byteCount() - 1; if (byteIndex > highestIndex) {- throw std::invalid_argument(fmt::format(- "Byte index must be <= {} for addresses of type: {}",- highestIndex,- detail::familyNameStr(family())));+ throw std::invalid_argument(+ fmt::format(+ "Byte index must be <= {} for addresses of type: {}",+ highestIndex,+ detail::familyNameStr(family()))); } if (isV4()) { return asV4().bytes()[byteIndex];@@ -452,11 +455,12 @@ CIDRNetwork IPAddress::longestCommonPrefix( const CIDRNetwork& one, const CIDRNetwork& two) { if (one.first.family() != two.first.family()) {- throw std::invalid_argument(fmt::format(- "Can't compute longest common prefix between addresses of different"- "families. Passed: {} and {}",- detail::familyNameStr(one.first.family()),- detail::familyNameStr(two.first.family())));+ throw std::invalid_argument(+ fmt::format(+ "Can't compute longest common prefix between addresses of different"+ "families. Passed: {} and {}",+ detail::familyNameStr(one.first.family()),+ detail::familyNameStr(two.first.family()))); } if (one.first.isV4()) { auto prefix = IPAddressV4::longestCommonPrefix(
@@ -111,9 +111,10 @@ IPAddressV4 IPAddressV4::fromBinary(ByteRange bytes) { auto maybeIp = tryFromBinary(bytes); if (maybeIp.hasError()) {- throw IPAddressFormatException(to<std::string>(- "Invalid IPv4 binary data: length must be 4 bytes, got ",- bytes.size()));+ throw IPAddressFormatException(+ to<std::string>(+ "Invalid IPv4 binary data: length must be 4 bytes, got ",+ bytes.size())); } return maybeIp.value(); }@@ -146,7 +147,7 @@ fmt::format("input does not end with '.in-addr.arpa': '{}'", arpaname)); } std::vector<StringPiece> pieces;- split(".", piece, pieces);+ split('.', piece, pieces); if (pieces.size() != 4) { throw IPAddressFormatException(fmt::format("Invalid input. Got {}", piece)); }@@ -277,10 +278,11 @@ uint8_t IPAddressV4::getNthMSByte(size_t byteIndex) const { const auto highestIndex = byteCount() - 1; if (byteIndex > highestIndex) {- throw std::invalid_argument(fmt::format(- "Byte index must be <= {} for addresses of type: {}",- highestIndex,- detail::familyNameStr(AF_INET)));+ throw std::invalid_argument(+ fmt::format(+ "Byte index must be <= {} for addresses of type: {}",+ highestIndex,+ detail::familyNameStr(AF_INET))); } return bytes()[byteIndex]; }
@@ -46,12 +46,12 @@ /** * Pair of IPAddressV4, netmask */-typedef std::pair<IPAddressV4, uint8_t> CIDRNetworkV4;+using CIDRNetworkV4 = std::pair<IPAddressV4, uint8_t>; /** * Specialization of `std::array` for IPv4 addresses */-typedef std::array<uint8_t, 4> ByteArray4;+using ByteArray4 = std::array<uint8_t, 4>; class IPAddressV4 { public:
@@ -177,9 +177,10 @@ IPAddressV6 IPAddressV6::fromBinary(ByteRange bytes) { auto maybeIp = tryFromBinary(bytes); if (maybeIp.hasError()) {- throw IPAddressFormatException(to<std::string>(- "Invalid IPv6 binary data: length must be 16 bytes, got ",- bytes.size()));+ throw IPAddressFormatException(+ to<std::string>(+ "Invalid IPv6 binary data: length must be 16 bytes, got ",+ bytes.size())); } return maybeIp.value(); }@@ -208,11 +209,12 @@ IPAddressV6 IPAddressV6::fromInverseArpaName(const std::string& arpaname) { auto piece = StringPiece(arpaname); if (!piece.removeSuffix(".ip6.arpa")) {- throw IPAddressFormatException(fmt::format(- "Invalid input. Should end with 'ip6.arpa'. Got '{}'", arpaname));+ throw IPAddressFormatException(+ fmt::format(+ "Invalid input. Should end with 'ip6.arpa'. Got '{}'", arpaname)); } std::vector<StringPiece> pieces;- split(".", piece, pieces);+ split('.', piece, pieces); if (pieces.size() != 32) { throw IPAddressFormatException( fmt::format("Invalid input. Got '{}'", piece));@@ -446,10 +448,11 @@ char buffer[INET6_ADDRSTRLEN + IFNAMSIZ + 1]; if (!inet_ntop(AF_INET6, toAddr().s6_addr, buffer, INET6_ADDRSTRLEN)) {- throw IPAddressFormatException(fmt::format(- "Invalid address with hex '{}' with error {}",- detail::Bytes::toHex(bytes(), 16),- errnoStr(errno)));+ throw IPAddressFormatException(+ fmt::format(+ "Invalid address with hex '{}' with error {}",+ detail::Bytes::toHex(bytes(), 16),+ errnoStr(errno))); } auto scopeId = getScopeId();@@ -496,10 +499,11 @@ uint8_t IPAddressV6::getNthMSByte(size_t byteIndex) const { const auto highestIndex = byteCount() - 1; if (byteIndex > highestIndex) {- throw std::invalid_argument(fmt::format(- "Byte index must be <= {} for addresses of type: {}",- highestIndex,- detail::familyNameStr(AF_INET6)));+ throw std::invalid_argument(+ fmt::format(+ "Byte index must be <= {} for addresses of type: {}",+ highestIndex,+ detail::familyNameStr(AF_INET6))); } return bytes()[byteIndex]; }
@@ -51,12 +51,12 @@ /** * Pair of IPAddressV6, netmask */-typedef std::pair<IPAddressV6, uint8_t> CIDRNetworkV6;+using CIDRNetworkV6 = std::pair<IPAddressV6, uint8_t>; /** * Specialization for `std::array` for IPv6 addresses */-typedef std::array<uint8_t, 16> ByteArray16;+using ByteArray16 = std::array<uint8_t, 16>; class IPAddressV6 { public:@@ -81,7 +81,7 @@ /** * Alias std::runtime_error, to be thrown when a type assertion fails */- typedef std::runtime_error TypeError;+ using TypeError = std::runtime_error; /** * The binary prefix for Teredo networks
@@ -16,8 +16,6 @@ #pragma once -#include <cassert>-#include <new> #include <type_traits> #include <utility>
@@ -157,10 +157,10 @@ template <typename> class Atom = std::atomic, typename Traits = IndexedMemPoolTraits<T>> struct IndexedMemPool {- typedef T value_type;+ using value_type = T; - typedef std::unique_ptr<T, detail::IndexedMemPoolRecycler<IndexedMemPool>>- UniquePtr;+ using UniquePtr =+ std::unique_ptr<T, detail::IndexedMemPoolRecycler<IndexedMemPool>>; IndexedMemPool(const IndexedMemPool&) = delete; IndexedMemPool& operator=(const IndexedMemPool&) = delete;@@ -181,9 +181,10 @@ static constexpr uint32_t maxIndexForCapacity(uint32_t capacity) { // index of std::numeric_limits<uint32_t>::max() is reserved for isAllocated // tracking- return uint32_t(std::min(- uint64_t(capacity) + (NumLocalLists - 1) * LocalListLimit,- uint64_t(std::numeric_limits<uint32_t>::max() - 1)));+ return uint32_t(+ std::min(+ uint64_t(capacity) + (NumLocalLists - 1) * LocalListLimit,+ uint64_t(std::numeric_limits<uint32_t>::max() - 1))); } static constexpr uint32_t capacityForMaxIndex(uint32_t maxIndex) {
@@ -14,4 +14,4 @@ * limitations under the License. */ -#include <folly/container/IntrusiveList.h>+#include <folly/container/IntrusiveList.h> // @shim
@@ -89,7 +89,7 @@ template <class Func> struct Lazy {- typedef invoke_result_t<Func> result_type;+ using result_type = invoke_result_t<Func>; static_assert( !std::is_const<Func>::value, "Func should not be a const-qualified type");
@@ -246,8 +246,9 @@ /** * Pop an element from (the final stage of) the pipeline. Blocking. */- void blockingRead(typename std::tuple_element<sizeof...(Stages), StageTuple>::- type::value_type& elem) {+ void blockingRead(+ typename std::tuple_element<sizeof...(Stages), StageTuple>::type::+ value_type& elem) { std::get<sizeof...(Stages)>(stages_).blockingRead(elem); } @@ -255,8 +256,9 @@ * Try to pop an element from (the final stage of) the pipeline. * Non-blocking. */- bool read(typename std::tuple_element<sizeof...(Stages), StageTuple>::type::- value_type& elem) {+ bool read(+ typename std::tuple_element<sizeof...(Stages), StageTuple>::type::+ value_type& elem) { return std::get<sizeof...(Stages)>(stages_).read(elem); }
@@ -23,9 +23,11 @@ #include <limits> #include <type_traits> +#include <folly/Portability.h> #include <folly/Traits.h> #include <folly/concurrency/CacheLocality.h> #include <folly/detail/TurnSequencer.h>+#include <folly/lang/Exception.h> #include <folly/portability/Unistd.h> namespace folly {@@ -85,11 +87,7 @@ /// enforced at compile time using type traits, which requires that T be /// adorned with accurate noexcept information. If your type does not /// use noexcept, you will have to wrap it in something that provides-/// the guarantee. We provide an alternate safe implementation for types-/// that don't use noexcept but that are marked folly::IsRelocatable-/// and std::is_nothrow_constructible, which is common for folly types.-/// In particular, if you can declare FOLLY_ASSUME_FBVECTOR_COMPATIBLE-/// then your type can be put in MPMCQueue.+/// the guarantee. /// /// If you have a pool of N queue consumers that you want to shut down /// after the queue has drained, one way is to enqueue N sentinel values@@ -106,6 +104,9 @@ : public detail::MPMCQueueBase<MPMCQueue<T, Atom, Dynamic, Allocator>>, std::allocator_traits<Allocator>::template rebind_alloc< detail::SingleElementQueue<T, Atom>> {+ static_assert(std::is_nothrow_move_constructible_v<T>);+ static_assert(std::is_nothrow_destructible_v<T>);+ friend class detail::MPMCPipelineStageImpl<T>; using Base = detail::MPMCQueueBase<MPMCQueue<T, Atom, Dynamic, Allocator>>; using Slot = detail::SingleElementQueue<T, Atom>;@@ -682,15 +683,13 @@ template < template < typename T,- template <typename>- class Atom,+ template <typename> class Atom, bool Dynamic, class Allocator> class Derived, typename T,- template <typename>- class Atom,+ template <typename> class Atom, bool Dynamic, class Allocator> class MPMCQueueBase<Derived<T, Atom, Dynamic, Allocator>> {@@ -700,13 +699,11 @@ // template using DerivedType = Derived<T, Atom, Dynamic, Allocator>; - static_assert(- std::is_nothrow_constructible<T, T&&>::value ||- folly::IsRelocatable<T>::value,- "T must be relocatable or have a noexcept move constructor");+ static_assert(std::is_nothrow_move_constructible_v<T>);+ static_assert(std::is_nothrow_destructible_v<T>); public:- typedef T value_type;+ using value_type = T; using Slot = detail::SingleElementQueue<T, Atom>; @@ -719,11 +716,14 @@ pushSpinCutoff_(0), popSpinCutoff_(0) { if (queueCapacity == 0) {+#if FOLLY_HAS_EXCEPTIONS throw std::invalid_argument( "MPMCQueue with explicit capacity 0 is impossible" // Stride computation in derived classes would sigfpe if capacity is 0 );+#endif }+ FOLLY_SAFE_CHECK(queueCapacity > 0); // ideally this would be a static assert, but g++ doesn't allow it assert(@@ -1371,42 +1371,24 @@ } /// enqueue using in-place noexcept construction- template <- typename... Args,- typename = typename std::enable_if<- std::is_nothrow_constructible<T, Args...>::value>::type>+ template <typename... Args> void enqueue( const uint32_t turn, Atom<uint32_t>& spinCutoff, const bool updateSpinCutoff, Args&&... args) noexcept {+ static_assert(std::is_nothrow_constructible_v<T, Args...>); sequencer_.waitForTurn(turn * 2, spinCutoff, updateSpinCutoff);- new (&contents_) T(std::forward<Args>(args)...);+ new (&contents_) T(std::forward<Args>(args)...); // noexcept, per above sequencer_.completeTurn(turn * 2); } - /// enqueue using move construction, either real (if- /// is_nothrow_move_constructible) or simulated using relocation and- /// default construction (if IsRelocatable and is_nothrow_constructible)- template <- typename = typename std::enable_if<- (folly::IsRelocatable<T>::value &&- std::is_nothrow_constructible<T>::value) ||- std::is_nothrow_constructible<T, T&&>::value>::type> void enqueue( const uint32_t turn, Atom<uint32_t>& spinCutoff, const bool updateSpinCutoff, T&& goner) noexcept {- enqueueImpl(- turn,- spinCutoff,- updateSpinCutoff,- std::move(goner),- typename std::conditional<- std::is_nothrow_constructible<T, T&&>::value,- ImplByMove,- ImplByRelocation>::type());+ enqueueImpl(turn, spinCutoff, updateSpinCutoff, std::move(goner)); } /// Waits until either:@@ -1433,15 +1415,7 @@ Atom<uint32_t>& spinCutoff, const bool updateSpinCutoff, T& elem) noexcept {- dequeueImpl(- turn,- spinCutoff,- updateSpinCutoff,- elem,- typename std::conditional<- folly::IsRelocatable<T>::value,- ImplByRelocation,- ImplByMove>::type());+ dequeueImpl(turn, spinCutoff, updateSpinCutoff, elem); } /// Waits until either:@@ -1473,77 +1447,29 @@ T* ptr() noexcept { return static_cast<T*>(static_cast<void*>(&contents_)); } void destroyContents() noexcept {- try {- ptr()->~T();- } catch (...) {- // g++ doesn't seem to have std::is_nothrow_destructible yet- }+ ptr()->~T(); // assume noexcept if (kIsDebug) { memset(&contents_, 'Q', sizeof(T)); } } - /// Tag classes for dispatching to enqueue/dequeue implementation.- struct ImplByRelocation {};- struct ImplByMove {};- /// enqueue using nothrow move construction. void enqueueImpl( const uint32_t turn, Atom<uint32_t>& spinCutoff, const bool updateSpinCutoff,- T&& goner,- ImplByMove) noexcept {+ T&& goner) noexcept { sequencer_.waitForTurn(turn * 2, spinCutoff, updateSpinCutoff); new (&contents_) T(std::move(goner)); sequencer_.completeTurn(turn * 2); } - /// enqueue by simulating nothrow move with relocation, followed by- /// default construction to a noexcept relocation.- void enqueueImpl(- const uint32_t turn,- Atom<uint32_t>& spinCutoff,- const bool updateSpinCutoff,- T&& goner,- ImplByRelocation) noexcept {- sequencer_.waitForTurn(turn * 2, spinCutoff, updateSpinCutoff);- memcpy(- static_cast<void*>(&contents_),- static_cast<void const*>(&goner),- sizeof(T));- sequencer_.completeTurn(turn * 2);- new (&goner) T();- }-- /// dequeue by destructing followed by relocation. This version is preferred,- /// because as much work as possible can be done before waiting.- void dequeueImpl(- uint32_t turn,- Atom<uint32_t>& spinCutoff,- const bool updateSpinCutoff,- T& elem,- ImplByRelocation) noexcept {- try {- elem.~T();- } catch (...) {- // unlikely, but if we don't complete our turn the queue will die- }- sequencer_.waitForTurn(turn * 2 + 1, spinCutoff, updateSpinCutoff);- memcpy(- static_cast<void*>(&elem),- static_cast<void const*>(&contents_),- sizeof(T));- sequencer_.completeTurn(turn * 2 + 1);- }- /// dequeue by nothrow move assignment. void dequeueImpl( uint32_t turn, Atom<uint32_t>& spinCutoff, const bool updateSpinCutoff,- T& elem,- ImplByMove) noexcept {+ T& elem) noexcept { sequencer_.waitForTurn(turn * 2 + 1, spinCutoff, updateSpinCutoff); elem = std::move(*ptr()); destroyContents();
@@ -14,4 +14,4 @@ * limitations under the License. */ -#include <folly/container/MapUtil.h>+#include <folly/container/MapUtil.h> // @shim
@@ -35,7 +35,13 @@ public: /* implicit */ MaybeManagedPtr(T* t) : t_(std::shared_ptr<T>(std::shared_ptr<void>(), t)) {}- /* implicit */ MaybeManagedPtr(std::shared_ptr<T> t) : t_(t) {}+ /* implicit */ MaybeManagedPtr(std::shared_ptr<T> t) : t_(std::move(t)) {}++ /**+ * Construction from shared_ptr<X>, where X is derived from T.+ */+ template <typename Q, typename = std::enable_if_t<std::is_base_of_v<T, Q>>>+ /* implicit */ MaybeManagedPtr(std::shared_ptr<Q> q) : t_(std::move(q)) {} /** * Get pointer to the element contained in MaybeManagedPtr.
@@ -369,7 +369,7 @@ namespace detail { void weak_ptr_set_stored_ptr(std::weak_ptr<void>& w, void* ptr); -template <typename Tag, void* std::__weak_ptr<void>::*WeakPtr_Ptr_Field>+template <typename Tag, void* std::__weak_ptr<void>::* WeakPtr_Ptr_Field> struct GenerateWeakPtrInternalsAccessor { friend void weak_ptr_set_stored_ptr(std::weak_ptr<void>& w, void* ptr) { w.*WeakPtr_Ptr_Field = ptr;@@ -415,6 +415,50 @@ return std::shared_ptr<U>(r, ptr); #endif }++/**+ * fmap_shared_ptr_aliasing+ *+ * This is a helper method that allows one to get aliased shared_ptr to an inner+ * object inside another shared_ptr. For example, if you have an object of type+ * T that contains field of type U, you can use this method to get a shared+ * pointer to the field U by calling `fmap_shared_ptr_aliasing(ptrToT, getU)`+ * where `getU` is a function that returns a pointer to that field.+ * @param getU a function that returns a const pointer to the field of type U by+ * taking a `const T*` as an argument.+ * @return a shared_ptr to the field U or nullptr if the owner is+ * nullptr or getU returns nullptr.+ */+template <+ typename T,+ typename GetU,+ typename U = std::remove_pointer_t<std::invoke_result_t<GetU&, const T*>>>+std::shared_ptr<U> fmap_shared_ptr_aliasing(+ const std::shared_ptr<T>& owner, GetU getU) {+ if (auto* tPtr = owner.get()) {+ if (auto* uPtr = getU(tPtr)) {+ return to_shared_ptr_aliasing(owner, uPtr);+ }+ }+ return nullptr;+}++template <+ typename T,+ typename GetU,+ typename U = std::remove_pointer_t<std::invoke_result_t<GetU&, const T*>>>+std::shared_ptr<U> fmap_shared_ptr_aliasing(+ std::shared_ptr<T>&& owner, GetU getU) {+ if (auto* tPtr = owner.get()) {+ if (auto* uPtr = getU(tPtr)) {+ return to_shared_ptr_aliasing(owner, uPtr);+ }+ }+ return nullptr;+}++template <typename GetU>+auto fmap_shared_ptr_aliasing(std::nullptr_t owner, GetU&& getU) = delete; /** * copy_to_unique_ptr
@@ -17,9 +17,7 @@ #pragma once #include <cassert>-#include <climits> #include <cstdint>-#include <utility> #include <folly/Optional.h> #include <folly/Portability.h>@@ -304,5 +302,5 @@ } } -typedef MicroLockBase<> MicroLock;+using MicroLock = MicroLockBase<>; } // namespace folly
@@ -25,6 +25,7 @@ #include <folly/Optional.h> #include <folly/ScopeGuard.h> #include <folly/io/async/DestructorCheck.h>+#include <folly/lang/Switch.h> #include <folly/small_vector.h> /**@@ -166,21 +167,25 @@ if (iterating_) { CHECK(maybeCurrentIterationPolicy_.has_value()); const auto& policy = maybeCurrentIterationPolicy_.value();- switch (policy) {- case InvokeWhileIteratingPolicy::InvokeAdded:- case InvokeWhileIteratingPolicy::DoNotInvokeAdded:- break;- case InvokeWhileIteratingPolicy::CheckNoChange:- folly::terminate_with<std::runtime_error>(- "Cannot add observers while iterating "- "per current iteration policy (CheckNoChange)");- break;- case InvokeWhileIteratingPolicy::CheckNoAdded:- folly::terminate_with<std::runtime_error>(- "Cannot add observers while iterating "- "per current iteration policy (CheckNoAdded)");- break;- }+ FOLLY_EXHAUSTIVE_SWITCH({+ switch (policy) {+ case InvokeWhileIteratingPolicy::InvokeAdded:+ case InvokeWhileIteratingPolicy::DoNotInvokeAdded:+ break;+ case InvokeWhileIteratingPolicy::CheckNoChange:+ folly::terminate_with<std::runtime_error>(+ "Cannot add observers while iterating "+ "per current iteration policy (CheckNoChange)");+ break;+ case InvokeWhileIteratingPolicy::CheckNoAdded:+ folly::terminate_with<std::runtime_error>(+ "Cannot add observers while iterating "+ "per current iteration policy (CheckNoAdded)");+ break;+ default:+ folly::assume_unreachable();+ }+ }); } observers_.insert(observers_.end(), observer); return true;@@ -203,18 +208,22 @@ if (iterating_) { CHECK(maybeCurrentIterationPolicy_.has_value()); const auto& policy = maybeCurrentIterationPolicy_.value();- switch (policy) {- case InvokeWhileIteratingPolicy::InvokeAdded:- case InvokeWhileIteratingPolicy::DoNotInvokeAdded:- break;- case InvokeWhileIteratingPolicy::CheckNoChange:- folly::terminate_with<std::runtime_error>(- "Cannot remove observers while iterating "- "per current iteration policy (CheckNoChange)");- break;- case InvokeWhileIteratingPolicy::CheckNoAdded:- break;- }+ FOLLY_EXHAUSTIVE_SWITCH({+ switch (policy) {+ case InvokeWhileIteratingPolicy::InvokeAdded:+ case InvokeWhileIteratingPolicy::DoNotInvokeAdded:+ break;+ case InvokeWhileIteratingPolicy::CheckNoChange:+ folly::terminate_with<std::runtime_error>(+ "Cannot remove observers while iterating "+ "per current iteration policy (CheckNoChange)");+ break;+ case InvokeWhileIteratingPolicy::CheckNoAdded:+ break;+ default:+ folly::assume_unreachable();+ }+ }); *it = nullptr; removalDuringIteration_ = true;
@@ -22,7 +22,7 @@ /// IMPORTANT: `folly`-internal, do NOT use this in new user code. Instead: ///-/// - `co_yield coro::co_cancelled` to signal that a coro was cancelled.+/// - `co_yield coro::co_stopped_may_throw` to signal a coro was cancelled. /// /// - To check for cancellation in `folly::coro` coroutines, use one of: /// // (1) default behavior@@ -31,7 +31,7 @@ /// auto& ctok = co_await coro::co_current_cancellation_token; /// if (ctok.isCancellationRequested()) { /// /* ... do stuff ... */-/// co_yield coro::co_cancelled;+/// co_yield coro::co_stopped_may_throw; /// } /// /// - Store `stopped_result` to obtain a `result<T>` or `non_value_result`@@ -40,22 +40,32 @@ /// - To avoid depending on `OperationCancelled` in code that would do /// `try-catch` in `folly::coro` coroutines, do this instead: ///-/// auto res = co_await coro::co_await_result(mightGetCancelled());-/// if (auto* ex = get_exception<MyErr>(res)) {-/// // HANDLE ERROR HERE+/// auto res = co_await coro::value_or_error_or_stopped(+/// mightGetCancelled());+/// if (auto ex = get_exception<MyErr>(res)) {+/// // Handle error here; `ex` quacks like `const MyErr*`. /// } else if (res.has_stopped()) {-/// // HANDLE CANCELLATION HERE-/// co_yield coro::co_cancelled;+/// // Handle cancellation here+/// co_yield coro::co_stopped_may_throw; /// } else { // get value, or propagate unhandled errors/cancellation-/// auto v = co_await coro::co_ready(std::move(res));+/// auto v = co_await folly::or_unwind(std::move(res)); /// } ///+/// OR: If you want to transparently propagate cancellation to the parent+/// coro, use `value_or_error()` instead. Then, a "cancellation" /+/// "stopped" completion will promptly end the current coro WITHOUT+/// throwing, and its parent will see a "stopped" completion as well. As+/// with `co_nothrow`, ask for help in Coroutines@ if your current scope+/// requires async cleanup / async RAII (such as "async scope" usage or+/// other background work).+/// /// Rationale / purpose: For now, `folly` uses the `OperationCancelled` /// exception to signal "this work was stopped". However, as of C++26 (see-/// P2300, plus arguments in P1677), standard C++ differentiates between-/// "value", "error", and "stopped" completions. Therefore, we ask end-user-/// code to use the above cancellation-specific constructs, WITHOUT assuming-/// that cancellation / stopping is implemented as an exception.+/// https://wg21.link/P2300, per the arguments in https://wg21.link/P1677),+/// standard C++ differentiates between "value", "error", and "stopped"+/// completions. Therefore, we ask end-user code to use the above+/// cancellation-specific constructs, WITHOUT assuming that cancellation /+/// stopping is implemented as an exception. struct OperationCancelled final : public std::exception { const char* what() const noexcept override { return "coroutine operation cancelled";
@@ -110,7 +110,7 @@ template <class Value> class Optional { public:- typedef Value value_type;+ using value_type = Value; using promise_type = detail::OptionalPromise<Value>; @@ -220,11 +220,19 @@ } std::optional<Value> toStdOptional() && noexcept {- return static_cast<std::optional<Value>>(std::move(*this));+ if (has_value()) {+ auto opt = std::optional<Value>(std::move(value()));+ reset();+ return opt;+ }+ return std::nullopt; } std::optional<Value> toStdOptional() const& noexcept {- return static_cast<std::optional<Value>>(*this);+ if (has_value()) {+ return std::optional<Value>(value());+ }+ return std::nullopt; } /// Set the Optional@@ -477,7 +485,7 @@ }; bool hasValue; - FOLLY_CXX20_CONSTEXPR StorageNonTriviallyDestructible() : hasValue{false} {}+ constexpr StorageNonTriviallyDestructible() : hasValue{false} {} ~StorageNonTriviallyDestructible() { clear(); } void clear() {@@ -736,7 +744,8 @@ // Explicitly only allow suspension into an OptionalPromise template <typename U>- void await_suspend(coro::coroutine_handle<OptionalPromise<U>> h) const {+ void await_suspend(+ coro::coroutine_handle<OptionalPromise<U>> h) const noexcept { // Abort the rest of the coroutine. resume() is not going to be called h.destroy(); }
@@ -65,7 +65,7 @@ // This just allows using this class even with T=void. Attempting // to use the operator* or operator[] on a PackedSyncPtr<void> will // still properly result in a compile error.- typedef typename std::add_lvalue_reference<T>::type reference;+ using reference = typename std::add_lvalue_reference<T>::type; public: /*
@@ -61,7 +61,7 @@ std::is_trivial_v<T> && sizeof(T) <= NS && NS % alignof(T) == 0); public:- typedef T value_type;+ using value_type = T; static constexpr size_t kNodeSize = NS; static constexpr size_t kElementCount = NS / sizeof(T); static constexpr size_t kPaddingBytes = NS % sizeof(T);@@ -118,15 +118,6 @@ } storage_; }; -// We must define kElementCount and kPaddingBytes to work around a bug-// in gtest that odr-uses them.-template <class T, size_t NS>-constexpr size_t Node<T, NS>::kNodeSize;-template <class T, size_t NS>-constexpr size_t Node<T, NS>::kElementCount;-template <class T, size_t NS>-constexpr size_t Node<T, NS>::kPaddingBytes;- template <class Iter> class Iterator; @@ -137,8 +128,7 @@ // Helper class template to define a base class for Iterator (below) and save // typing. template <- template <class>- class Class,+ template <class> class Class, class Iter, class Traits = std::iterator_traits<Iter>, class Ref = typename Traits::reference,@@ -286,14 +276,14 @@ template <class Container> class Adaptor { public:- typedef typename Container::value_type Node;- typedef typename Node::value_type value_type;- typedef value_type& reference;- typedef const value_type& const_reference;- typedef Iterator<typename Container::iterator> iterator;- typedef Iterator<typename Container::const_iterator> const_iterator;- typedef typename const_iterator::difference_type difference_type;- typedef typename Container::size_type size_type;+ using Node = typename Container::value_type;+ using value_type = typename Node::value_type;+ using reference = value_type&;+ using const_reference = const value_type&;+ using iterator = Iterator<typename Container::iterator>;+ using const_iterator = Iterator<typename Container::const_iterator>;+ using difference_type = typename const_iterator::difference_type;+ using size_type = typename Container::size_type; static constexpr size_t kElementsPerNode = Node::kElementCount; // Constructors
@@ -216,9 +216,8 @@ class Poly, typename... As, std::enable_if_t<detail::IsPoly<Poly>::value, int> = 0>-auto poly_call(Poly&& _this, As&&... as)- -> decltype(poly_call<N, I>(- static_cast<Poly&&>(_this).get(), static_cast<As&&>(as)...)) {+auto poly_call(Poly&& _this, As&&... as) -> decltype(poly_call<N, I>(+ static_cast<Poly&&>(_this).get(), static_cast<As&&>(as)...)) { return poly_call<N, I>( static_cast<Poly&&>(_this).get(), static_cast<As&&>(as)...); }
@@ -78,22 +78,6 @@ __attribute__((__format__(__printf__, format_param, dots_param))) #endif -// warn unused result-#if defined(__has_cpp_attribute)-#if __has_cpp_attribute(nodiscard)-#if defined(__clang__) || defined(__GNUC__)-#if __clang_major__ >= 10 || __GNUC__ >= 10-// early clang and gcc both warn on [[nodiscard]] when applied to class ctors-// easiest option is just to avoid emitting [[nodiscard]] under early clang/gcc-#define FOLLY_NODISCARD [[nodiscard]]-#endif-#endif-#endif-#endif-#ifndef FOLLY_NODISCARD-#define FOLLY_NODISCARD-#endif- // older clang-format gets confused by [[deprecated(...)]] on class decls #define FOLLY_DEPRECATED(...) [[deprecated(__VA_ARGS__)]] @@ -104,6 +88,13 @@ #define FOLLY_TARGET_ATTRIBUTE(target) __attribute__((__target__(target))) #endif +#if defined(__i386__) || defined(__i686__) || defined(__x86__) || \+ defined(_M_IX86)+#define FOLLY_X86 1+#else+#define FOLLY_X86 0+#endif+ // detection for 64 bit #if defined(__x86_64__) || defined(_M_X64) #define FOLLY_X64 1@@ -149,6 +140,7 @@ namespace folly { constexpr bool kIsArchArm = FOLLY_ARM == 1;+constexpr bool kIsArchX86 = FOLLY_X86 == 1; constexpr bool kIsArchAmd64 = FOLLY_X64 == 1; constexpr bool kIsArchAArch64 = FOLLY_AARCH64 == 1; constexpr bool kIsArchPPC64 = FOLLY_PPC64 == 1;@@ -499,12 +491,14 @@ constexpr auto kIsMobile = false; #endif -#if defined(__linux__) && !FOLLY_MOBILE-constexpr auto kIsLinux = true;+#if defined(__linux__)+constexpr auto kIsLinuxActual = true; #else-constexpr auto kIsLinux = false;+constexpr auto kIsLinuxActual = false; #endif +constexpr auto kIsLinux = kIsLinuxActual && !kIsMobile;+ #if defined(__FreeBSD__) constexpr auto kIsFreeBSD = true; #else@@ -517,6 +511,12 @@ constexpr auto kIsWindows = false; #endif +#if defined(__ANDROID__)+constexpr auto kIsAndroid = true;+#else+constexpr auto kIsAndroid = false;+#endif+ #if defined(__APPLE__) constexpr auto kIsApple = true; #else@@ -620,16 +620,6 @@ #define FOLLY_STORAGE_CONSTEXPR constexpr #endif -// FOLLY_CXX20_CONSTEXPR-//-// C++20 permits more cases to be marked constexpr, including constructors that-// leave members uninitialized and virtual functions.-#if FOLLY_CPLUSPLUS >= 202002L-#define FOLLY_CXX20_CONSTEXPR constexpr-#else-#define FOLLY_CXX20_CONSTEXPR-#endif- // FOLLY_CXX23_CONSTEXPR // // C++23 permits more cases to be marked constexpr, including definitions of@@ -699,9 +689,13 @@ // - 14 is still bad: https://godbolt.org/z/nW1W8cWvb // - 15.0.0 is good: https://godbolt.org/z/Tco4c9hbq and sEaKKTf8r #define FOLLY_HAS_IMMOVABLE_COROUTINES 0+// On Windows, Clang with -fms-compatibility defines _MSC_FULL_VER to+// emulate MSVC - even for newer versions of Clang.+// Explicitly allow Clang 15+, prior to checking _MSC_FULL_VER+#elif defined(__clang_major__) && __clang_major__ >= 15+#define FOLLY_HAS_IMMOVABLE_COROUTINES 1 // BEWARE: Older versions of Clang pretend to be MSVC and define-// `_MSC_FULL_VER`, but fortunately none of clang 15, 16, 17, 18, 19 do this,-// so this branch should not result in a false-negative.+// `_MSC_FULL_VER` #elif defined(_MSC_FULL_VER) && _MSC_FULL_VER <= 192930040 // - 192930040 is bad: https://godbolt.org/z/E797W8xTT // - 192930153 is good: https://godbolt.org/z/cM4nW5rTK
@@ -34,7 +34,7 @@ */ template <class T> struct ProducerConsumerQueue {- typedef T value_type;+ using value_type = T; ProducerConsumerQueue(const ProducerConsumerQueue&) = delete; ProducerConsumerQueue& operator=(const ProducerConsumerQueue&) = delete;
@@ -22,65 +22,28 @@ namespace detail { -// Return the state size needed by RNG, expressed as a number of uint32_t-// integers. Specialized for all templates specified in the C++11 standard.-// For some (mersenne_twister_engine), this is exported as a state_size static-// data member; for others, the standard shows formulas.--template <class RNG, typename = void>-struct StateSize {- // A sane default.- using type = std::integral_constant<size_t, 512>;-};--template <class RNG>-struct StateSize<RNG, void_t<decltype(RNG::state_size)>> {- using type = std::integral_constant<size_t, RNG::state_size>;-};--template <class UIntType, UIntType a, UIntType c, UIntType m>-struct StateSize<std::linear_congruential_engine<UIntType, a, c, m>> {- // From the standard [rand.eng.lcong], this is ceil(log2(m) / 32) + 3,- // which is the same as ceil(ceil(log2(m) / 32) + 3, and- // ceil(log2(m)) <= std::numeric_limits<UIntType>::digits- using type = std::integral_constant<- size_t,- (std::numeric_limits<UIntType>::digits + 31) / 32 + 3>;-};--template <class UIntType, size_t w, size_t s, size_t r>-struct StateSize<std::subtract_with_carry_engine<UIntType, w, s, r>> {- // [rand.eng.sub]: r * ceil(w / 32)- using type = std::integral_constant<size_t, r*((w + 31) / 32)>;-};--template <typename RNG>-using StateSizeT = _t<StateSize<RNG>>;--template <class RNG>-struct SeedData {- SeedData() {- Random::secureRandom(seedData.data(), seedData.size() * sizeof(uint32_t));+struct SeedSeqSecureRandom {+ using result_type = uint32_t;+ template <typename Word>+ void generate(Word* b, Word* e) {+ static_assert(is_non_bool_integral_v<Word>);+ static_assert(sizeof(Word) >= sizeof(result_type));+ Random::secureRandom(b, (e - b) * sizeof(Word)); }-- static constexpr size_t stateSize = StateSizeT<RNG>::value;- std::array<uint32_t, stateSize> seedData; }; } // namespace detail template <class RNG, class /* EnableIf */> void Random::seed(RNG& rng) {- detail::SeedData<RNG> sd;- std::seed_seq s(std::begin(sd.seedData), std::end(sd.seedData));- rng.seed(s);+ detail::SeedSeqSecureRandom seq;+ rng.seed(seq); } template <class RNG, class /* EnableIf */> auto Random::create() -> RNG {- detail::SeedData<RNG> sd;- std::seed_seq s(std::begin(sd.seedData), std::end(sd.seedData));- return RNG(s);+ detail::SeedSeqSecureRandom seq;+ return RNG(seq); } } // namespace folly
@@ -21,7 +21,7 @@ /** * Range abstraction using a pair of iterators. It is not * similar to boost's range abstraction because an API identical- * with the former StringPiece class is required, which is used alot+ * with the former StringPiece class is required, which is used a lot * internally. This abstraction does fulfill the needs of boost's * range-oriented algorithms though. *@@ -152,6 +152,19 @@ using type = int; }; +#if defined(__cpp_char8_t) && __cpp_char8_t >= 201811L+template <>+struct IsCharPointer<char8_t*> {+ using type = int;+};++template <>+struct IsCharPointer<const char8_t*> {+ using const_type = int;+ using type = int;+};+#endif+ template <class T> struct IsUnsignedCharPointer {}; @@ -577,7 +590,7 @@ } /**- * Reset start and end interator based on size+ * Reset start and end iterator based on size */ void reset(Iter start, size_type size) { b_ = start;@@ -957,7 +970,7 @@ return find(other) != std::string::npos; } - void swap(Range& rhs) {+ void swap(Range& rhs) noexcept(std::is_nothrow_swappable_v<Iter>) { std::swap(b_, rhs.b_); std::swap(e_, rhs.e_); }@@ -1262,7 +1275,8 @@ const typename Range<Iter>::size_type Range<Iter>::npos = std::string::npos; template <class Iter>-void swap(Range<Iter>& lhs, Range<Iter>& rhs) {+void swap(Range<Iter>& lhs, Range<Iter>& rhs) noexcept(+ noexcept(lhs.swap(rhs))) { lhs.swap(rhs); }
@@ -157,8 +157,9 @@ dtor_mixin& operator=(dtor_mixin&&) = default; dtor_mixin& operator=(dtor_mixin const&) = default; ~dtor_mixin() noexcept(std::is_nothrow_destructible<T>::value) {- T* destruct_ptr = std::launder(reinterpret_cast<T*>(- reinterpret_cast<Replaceable<T>*>(this)->storage_));+ T* destruct_ptr = std::launder(+ reinterpret_cast<T*>(+ reinterpret_cast<Replaceable<T>*>(this)->storage_)); destruct_ptr->~T(); } };@@ -282,8 +283,9 @@ operator=(move_assignment_mixin&& other) noexcept( std::is_nothrow_destructible<T>::value && std::is_nothrow_move_constructible<T>::value) {- T* destruct_ptr = std::launder(reinterpret_cast<T*>(- reinterpret_cast<Replaceable<T>*>(this)->storage_));+ T* destruct_ptr = std::launder(+ reinterpret_cast<T*>(+ reinterpret_cast<Replaceable<T>*>(this)->storage_)); destruct_ptr->~T(); ::new (reinterpret_cast<Replaceable<T>*>(this)->storage_) T(*std::move(reinterpret_cast<Replaceable<T>&>(other)));@@ -346,8 +348,9 @@ operator=(copy_assignment_mixin const& other) noexcept( std::is_nothrow_destructible<T>::value && std::is_nothrow_copy_constructible<T>::value) {- T* destruct_ptr = std::launder(reinterpret_cast<T*>(- reinterpret_cast<Replaceable<T>*>(this)->storage_));+ T* destruct_ptr = std::launder(+ reinterpret_cast<T*>(+ reinterpret_cast<Replaceable<T>*>(this)->storage_)); destruct_ptr->~T(); ::new (reinterpret_cast<Replaceable<T>*>(this)->storage_) T(*reinterpret_cast<Replaceable<T> const&>(other));
@@ -209,7 +209,7 @@ * @refcode folly/docs/examples/folly/ScopeGuard2.cpp */ template <typename F>-FOLLY_NODISCARD detail::ScopeGuardImplDecay<F, true> makeGuard(F&& f) noexcept(+[[nodiscard]] detail::ScopeGuardImplDecay<F, true> makeGuard(F&& f) noexcept( noexcept(detail::ScopeGuardImplDecay<F, true>(static_cast<F&&>(f)))) { return detail::ScopeGuardImplDecay<F, true>(static_cast<F&&>(f)); }@@ -223,7 +223,7 @@ * @refcode folly/docs/examples/folly/ScopeGuard2.cpp */ template <typename F>-FOLLY_NODISCARD detail::ScopeGuardImplDecay<F, true>+[[nodiscard]] detail::ScopeGuardImplDecay<F, true> makeDismissedGuard(F&& f) noexcept( noexcept(detail::ScopeGuardImplDecay<F, true>( static_cast<F&&>(f), detail::ScopeGuardDismissed{}))) {
@@ -63,9 +63,9 @@ // reader-writer locks in use at Facebook for almost all use cases, // sometimes by a wide margin. (If it is rare that there are actually // concurrent readers then RWSpinLock can be a few nanoseconds faster.)-// I compared it to folly::RWSpinLock, folly::RWTicketSpinLock64,-// boost::shared_mutex, pthread_rwlock_t, and a RWLock that internally uses-// spinlocks to guard state and pthread_mutex_t+pthread_cond_t to block.+// I compared it to folly::RWSpinLock, boost::shared_mutex,+// pthread_rwlock_t, and a RWLock that internally uses spinlocks to guard+// state and pthread_mutex_t+pthread_cond_t to block. // (Thrift's ReadWriteMutex is based underneath on pthread_rwlock_t.) // It is generally as good or better than the rest when evaluating size, // speed, scalability, or latency outliers. In the corner cases where@@ -962,9 +962,7 @@ // that are the same after integer division by k share that resource. // Our strategy for deferred readers is to probe up to numSlots/4 slots, // using the full granularity of AccessSpreader for the start slot- // and then search outward. We can use AccessSpreader::current(n)- // without managing our own spreader if kMaxDeferredReaders <=- // AccessSpreader::kMaxCpus, which is currently 128.+ // and then search outward. // // In order to give each L1 cache its own playground, we need // kMaxDeferredReaders >= #L1 caches. We double it, making it@@ -1439,8 +1437,7 @@ template < bool ReaderPriority, typename Tag_,- template <typename>- class Atom,+ template <typename> class Atom, typename Policy> alignas(hardware_destructive_interference_size) typename SharedMutexImpl<ReaderPriority, Tag_, Atom, Policy>::@@ -1452,8 +1449,7 @@ template < bool ReaderPriority, typename Tag_,- template <typename>- class Atom,+ template <typename> class Atom, typename Policy> bool SharedMutexImpl<ReaderPriority, Tag_, Atom, Policy>:: tryUnlockTokenlessSharedDeferred() {@@ -1477,8 +1473,7 @@ template < bool ReaderPriority, typename Tag_,- template <typename>- class Atom,+ template <typename> class Atom, typename Policy> template <class WaitContext> bool SharedMutexImpl<ReaderPriority, Tag_, Atom, Policy>::lockSharedImpl(@@ -1621,8 +1616,7 @@ template < bool ReaderPriority, typename Tag_,- template <typename>- class Atom,+ template <typename> class Atom, typename Policy> class shared_lock< ::folly::SharedMutexImpl<ReaderPriority, Tag_, Atom, Policy>> {@@ -1633,7 +1627,7 @@ shared_lock() noexcept = default; - FOLLY_NODISCARD explicit shared_lock(mutex_type& mutex)+ [[nodiscard]] explicit shared_lock(mutex_type& mutex) : mutex_(std::addressof(mutex)) { lock(); }@@ -1641,18 +1635,18 @@ shared_lock(mutex_type& mutex, std::defer_lock_t) noexcept : mutex_(std::addressof(mutex)) {} - FOLLY_NODISCARD shared_lock(mutex_type& mutex, std::try_to_lock_t)+ [[nodiscard]] shared_lock(mutex_type& mutex, std::try_to_lock_t) : mutex_(std::addressof(mutex)) { try_lock(); } - FOLLY_NODISCARD shared_lock(mutex_type& mutex, std::adopt_lock_t)+ [[nodiscard]] shared_lock(mutex_type& mutex, std::adopt_lock_t) : mutex_(std::addressof(mutex)) { token_.state_ = token_type::State::LockedShared; } template <typename Clock, typename Duration>- FOLLY_NODISCARD shared_lock(+ [[nodiscard]] shared_lock( mutex_type& mutex, const std::chrono::time_point<Clock, Duration>& deadline) : mutex_(std::addressof(mutex)) {@@ -1660,7 +1654,7 @@ } template <typename Rep, typename Period>- FOLLY_NODISCARD shared_lock(+ [[nodiscard]] shared_lock( mutex_type& mutex, const std::chrono::duration<Rep, Period>& timeout) : mutex_(std::addressof(mutex)) { try_lock_for(timeout);@@ -1727,13 +1721,13 @@ return std::exchange(mutex_, nullptr); } - FOLLY_NODISCARD bool owns_lock() const noexcept {+ [[nodiscard]] bool owns_lock() const noexcept { return static_cast<bool>(token_); } explicit operator bool() const noexcept { return owns_lock(); } - FOLLY_NODISCARD mutex_type* mutex() const noexcept { return mutex_; }+ [[nodiscard]] mutex_type* mutex() const noexcept { return mutex_; } private: void error_if_not_lockable() const {
@@ -240,34 +240,52 @@ detail::singletonWarnCreateCircularDependencyAndAbort(type()); } - std::lock_guard entry_lock(mutex_);- if (state_.load(std::memory_order_acquire) == SingletonHolderState::Living) {- return;- }- if (state_.load(std::memory_order_relaxed) ==- SingletonHolderState::LivingInChildAfterFork) {- if (vault_.failOnUseAfterFork_) {- LOG(DFATAL) << "Attempting to use singleton " << type().name()- << " in child process after fork";- } else {- LOG(ERROR) << "Attempting to use singleton " << type().name()- << " in child process after fork";- }- auto expected = SingletonHolderState::LivingInChildAfterFork;- state_.compare_exchange_strong(- expected,- SingletonHolderState::Living,- std::memory_order_relaxed,- std::memory_order_relaxed);+ // NOTE: Keeping the vault state lock at the top of the createInstance+ // function is important to not deadlock singleton creation in child+ // processes.+ //+ // We take and hold the vault state lock at the start to ensure that a write+ // lock for the vault state isn't taken during the time that this Singleton's+ // mutex_ is held, as it may result in a deadlock in creation of this+ // singleton in the future if a fork happens in-between the mutex_ being+ // acquired and the completion of this function that could "easily" happen if+ // there's contention on the vault state, as another thread may initiate to+ // destroyInstances in preparation for a fork+ //+ // This still doesn't 100% remove the risk of deadlock in child processes, as+ // it's possible that the vault state rlock here is acquired in another+ // thread before the fork happens. That would block the child process from+ // re-instantiating the singleton vault, as it would never be able to get a+ // write lock on it.+ auto vault_state = vault_.state_.rlock();+ if (vault_state->state == detail::SingletonVaultState::Type::Quiescing) { return; }- if (state_.load(std::memory_order_acquire) ==- SingletonHolderState::NotRegistered) {- detail::singletonWarnCreateUnregisteredAndAbort(type());- } - if (state_.load(std::memory_order_acquire) == SingletonHolderState::Living) {- return;+ std::lock_guard entry_lock(mutex_);+ switch (auto state = state_.load(std::memory_order_acquire)) {+ case SingletonHolderState::Living:+ return; // nothing to do+ case SingletonHolderState::LivingInChildAfterFork:+ if (vault_.failOnUseAfterFork_) {+ LOG(DFATAL) << "Attempting to use singleton " << type().name()+ << " in child process after fork";+ } else {+ LOG(ERROR) << "Attempting to use singleton " << type().name()+ << " in child process after fork";+ }+ state_.compare_exchange_strong(+ state,+ SingletonHolderState::Living,+ std::memory_order_relaxed,+ std::memory_order_relaxed);+ return; // just need to do the state transition+ case SingletonHolderState::NotRegistered:+ detail::singletonWarnCreateUnregisteredAndAbort(type()); // noreturn+ case SingletonHolderState::Dead:+ break; // recreate it below+ default:+ assume_unreachable(); // impossible state } SCOPE_EXIT {@@ -279,14 +297,10 @@ creating_thread_.store(std::this_thread::get_id(), std::memory_order_release); - auto state = vault_.state_.rlock(); if (vault_.type_.load(std::memory_order_relaxed) != SingletonVault::Type::Relaxed &&- !state->registrationComplete) {+ !vault_state->registrationComplete) { detail::singletonWarnCreateBeforeRegistrationCompleteAndAbort(type());- }- if (state->state == detail::SingletonVaultState::Type::Quiescing) {- return; } auto destroy_baton = std::make_shared<folly::Baton<>>();
@@ -36,6 +36,7 @@ #include <folly/lang/SafeAssert.h> #include <folly/portability/Config.h> #include <folly/portability/FmtCompile.h>+#include <folly/system/AtFork.h> // Before registrationComplete() we cannot assume that glog has been // initialized, so we need to use RAW_LOG for any message that may be logged // before that.@@ -43,6 +44,8 @@ #if !defined(_WIN32) && !defined(__APPLE__) && !defined(__ANDROID__) #define FOLLY_SINGLETON_HAVE_DLSYM 1+#else+#define FOLLY_SINGLETON_HAVE_DLSYM 0 #endif namespace folly {@@ -442,7 +445,7 @@ void SingletonVault::scheduleDestroyInstances() { // Add a dependency on folly::ThreadLocal to make sure all its static- // singletons are initalized first.+ // singletons are initialized first. threadlocal_detail::StaticMeta<void, void>::instance(); #if !defined(FOLLY_SINGLETON_SKIP_SCHEDULE_ATEXIT) || \ !FOLLY_SINGLETON_SKIP_SCHEDULE_ATEXIT
@@ -100,7 +100,7 @@ template < typename T, typename Tag = detail::DefaultTag,- typename Make = detail::DefaultMake<T>,+ typename Make = void, typename TLTag = std:: conditional_t<std::is_same<Tag, detail::DefaultTag>::value, void, Tag>> class SingletonThreadLocal {@@ -110,12 +110,14 @@ using State = detail::SingletonThreadLocalState; using LocalCache = State::LocalCache; - using Object = invoke_result_t<Make>;+ using MakeFn =+ std::conditional_t<std::is_void_v<Make>, detail::DefaultMake<T>, Make>;+ using Object = invoke_result_t<MakeFn>; static_assert(std::is_convertible<Object&, T&>::value, "inconvertible"); struct ObjectWrapper { // keep as first field in first base, to save 1 instr in the fast path- Object object{Make{}()};+ Object object{MakeFn{}()}; }; struct Wrapper : ObjectWrapper, State::Tracking { /* implicit */ operator T&() { return ObjectWrapper::object; }@@ -146,17 +148,31 @@ return wrapper; } + FOLLY_EXPORT FOLLY_ALWAYS_INLINE static LocalCache& getLocalCache() {+ static thread_local LocalCache cache;+ return cache;+ }+ public:- FOLLY_EXPORT FOLLY_ALWAYS_INLINE static T& get() {+ FOLLY_ALWAYS_INLINE static T& get() { if (kIsMobile) { return getWrapper(); }- static thread_local LocalCache cache;+ auto& cache = getLocalCache(); auto* object = static_cast<Object*>(cache.object); return FOLLY_LIKELY(!!object) ? *object : getSlow(cache).object; } static T* try_get() {+ if (!kIsMobile) {+ auto& cache = getLocalCache();+ if (auto* object = static_cast<Object*>(cache.object)) {+ return object;+ }+ }+ if (threadlocal_detail::StaticMetaBase::dying()) {+ return nullptr;+ } auto* wrapper = getWrapperTL().get_existing(); return wrapper ? &static_cast<T&>(*wrapper) : nullptr; }
@@ -295,10 +295,11 @@ return; #endif } else {- throw std::invalid_argument(fmt::format(- "SocketAddress::setFromSockaddr() called "- "with unsupported address type {}",- address->sa_family));+ throw std::invalid_argument(+ fmt::format(+ "SocketAddress::setFromSockaddr() called "+ "with unsupported address type {}",+ address->sa_family)); } // For IP addresses, use the IPAddress constructor
@@ -18,7 +18,6 @@ #include <sys/types.h> -#include <cassert> #include <cstddef> #include <iosfwd> #include <string>@@ -360,6 +359,34 @@ static SocketAddress makeFromPath(StringPiece path) { SocketAddress addr; addr.setFromPath(path);+ return addr;+ }++ /**+ * Construct a SocketAddress from a local port number.+ * see setFromLocalPort for more details.+ *+ * Raises std::invalid_argument on error.+ *+ * @param port The local port number (in host byte order)+ */+ static SocketAddress makeFromLocalPort(uint16_t port) {+ SocketAddress addr;+ addr.setFromLocalPort(port);+ return addr;+ }++ /**+ * Similar to makeFromLocalPort(uint16_t port), but accepts the port as+ * a std::string.+ *+ * Raises std::invalid_argument on error.+ *+ * @param port The local port number+ */+ static SocketAddress makeFromLocalPort(const char* port) {+ SocketAddress addr;+ addr.setFromLocalPort(port); return addr; }
@@ -289,6 +289,14 @@ void internalSplit( DelimT delim, StringPiece sp, OutputIterator out, bool ignoreEmpty); +/*+ * Count the number of tokens that would be produced by splitting+ * the given StringPiece with the given delimiter.+ * This is used for pre-allocation optimization in split().+ */+size_t delimCountTokens(char delim, StringPiece sp, bool ignoreEmpty);+size_t delimCountTokens(StringPiece delim, StringPiece sp, bool ignoreEmpty);+ template <class OutStringT, class Container> std::enable_if_t< IsSplitSupportedContainer<Container>::value &&@@ -498,6 +506,35 @@ StringPiece(input), std::back_inserter(out), ignoreEmpty);+}++template <class Delim, class String, class OutputType>+std::enable_if_t<detail::IsSplitSupportedContainer<OutputType>::value> split(+ const Delim& delimiter,+ const String& input,+ OutputType& out,+ const SplitOptions& options) {+ size_t predicted_count = 0;+ if (options.preallocate()) {+ // First pass: count the expected number of tokens+ StringPiece sp(input);+ auto delim_prepared = detail::prepareDelim(delimiter);+ predicted_count =+ detail::delimCountTokens(delim_prepared, sp, options.ignore_empty());+ grow_capacity_by(out, predicted_count);+ }++ size_t initial_size = out.size();+ detail::internalSplit<typename OutputType::value_type>(+ detail::prepareDelim(delimiter),+ StringPiece(input),+ std::back_inserter(out),+ options.ignore_empty());++ if (options.preallocate()) {+ [[maybe_unused]] size_t actual_count = out.size() - initial_size;+ assert(predicted_count == actual_count);+ } } template <
@@ -191,10 +191,11 @@ int bytes_used = stringAppendfImplHelper( inline_buffer.data(), inline_buffer.size(), format, args); if (bytes_used < 0) {- throw std::runtime_error(to<std::string>(- "Invalid format string; snprintf returned negative "- "with format string: ",- format));+ throw std::runtime_error(+ to<std::string>(+ "Invalid format string; snprintf returned negative "+ "with format string: ",+ format)); } if (static_cast<size_t>(bytes_used) < inline_buffer.size()) {@@ -366,6 +367,17 @@ {" ", 0}, {nullptr, 0}, }; +const PrettySuffix kPrettyBitsMetricSuffixes[] = {+ {"Eb", 1e18L},+ {"Pb", 1e15L},+ {"Tb", 1e12L},+ {"Gb", 1e9L},+ {"Mb", 1e6L},+ {"kb", 1e3L},+ {"b ", 0},+ {nullptr, 0},+};+ const PrettySuffix* const kPrettySuffixes[PRETTY_NUM_TYPES] = { kPrettyTimeSuffixes, kPrettyTimeHmsSuffixes,@@ -376,6 +388,7 @@ kPrettyUnitsBinarySuffixes, kPrettyUnitsBinaryIECSuffixes, kPrettySISuffixes,+ kPrettyBitsMetricSuffixes, }; } // namespace@@ -436,8 +449,9 @@ } } if (bestPrefixId == -1) { // No valid suffix rule found- throw std::invalid_argument(folly::to<std::string>(- "Unable to parse suffix \"", *prettyString, "\""));+ throw std::invalid_argument(+ folly::to<std::string>(+ "Unable to parse suffix \"", *prettyString, "\"")); } prettyString->advance(size_t(longestPrefixLen)); return suffixes[bestPrefixId].val != 0.@@ -722,7 +736,7 @@ std::string stripLeftMargin(std::string s) { std::vector<StringPiece> pieces;- split("\n", s, pieces);+ split('\n', s, pieces); auto piecer = range(pieces); auto piece = (piecer.end() - 1);@@ -768,6 +782,58 @@ const SubstringConversionCode& other) const { return this->code == other.code && this->substring == other.substring; }++namespace detail {++// Template implementation that both concrete overloads delegate to+template <class DelimT>+size_t delimCountTokensImpl(DelimT delim, StringPiece sp, bool ignoreEmpty) {+ assert(sp.empty() || sp.start() != nullptr);++ const char* s = sp.start();+ const size_t strSize = sp.size();+ const size_t dSize = delimSize(delim);++ if (dSize > strSize || dSize == 0) {+ return (!ignoreEmpty || strSize > 0) ? 1 : 0;+ }++ size_t tokenCount = 0;+ size_t tokenStartPos = 0;+ size_t tokenSize = 0;++ for (size_t i = 0; i <= strSize - dSize; ++i) {+ if (atDelim(&s[i], delim)) {+ if (!ignoreEmpty || tokenSize > 0) {+ ++tokenCount;+ }+ tokenStartPos = i + dSize;+ tokenSize = 0;+ i += dSize - 1;+ } else {+ ++tokenSize;+ }+ }++ // Count the final token+ tokenSize = strSize - tokenStartPos;+ if (!ignoreEmpty || tokenSize > 0) {+ ++tokenCount;+ }++ return tokenCount;+}++// Concrete overloads that delegate to the template implementation+size_t delimCountTokens(char delim, StringPiece sp, bool ignoreEmpty) {+ return delimCountTokensImpl(delim, sp, ignoreEmpty);+}++size_t delimCountTokens(StringPiece delim, StringPiece sp, bool ignoreEmpty) {+ return delimCountTokensImpl(delim, sp, ignoreEmpty);+}++} // namespace detail } // namespace folly
@@ -47,6 +47,45 @@ namespace folly { +/// SplitOptions+///+/// Options for controlling split() behavior. This class uses a builder pattern+/// to allow for easy configuration and method chaining.+class SplitOptions {+ public:+ struct Defaults {+ /// preallocate+ ///+ /// If true, split will count the expected number of tokens first+ /// and pre-allocate container capacity using grow_capacity_by.+ /// This can improve performance when splitting large strings with+ /// many tokens, but may add overhead for small strings.+ static constexpr bool preallocate = false;++ /// ignoreEmpty+ ///+ /// If true, adjacent delimiters are treated as one single separator+ /// (ignoring empty tokens), otherwise empty tokens are generated.+ static constexpr bool ignore_empty = false;+ };++ constexpr bool preallocate() const { return preallocate_; }+ constexpr SplitOptions& preallocate(bool enable) {+ preallocate_ = enable;+ return *this;+ }++ constexpr bool ignore_empty() const { return ignore_empty_; }+ constexpr SplitOptions& ignore_empty(bool enable) {+ ignore_empty_ = enable;+ return *this;+ }++ private:+ bool preallocate_ = Defaults::preallocate;+ bool ignore_empty_ = Defaults::ignore_empty;+};+ /** * @overloadbrief C-escape a string. *@@ -348,6 +387,10 @@ PRETTY_UNITS_BINARY_IEC, PRETTY_SI,++ PRETTY_BITS_METRIC,+ PRETTY_BITS = PRETTY_BITS_METRIC,+ PRETTY_NUM_TYPES, }; @@ -372,6 +415,9 @@ * PRETTY_UNITS_BINARY_IEC - Ki, Mi, Gi, etc * PRETTY_SI - full SI metric prefixes from yocto to Yotta * http://en.wikipedia.org/wiki/Metric_prefix+ * PRETTY_BITS_METRIC - kb, Mb, Gb, etc (goes up by 10^3 = 1000 each time)+ * Useful for network bandwidth (e.g., 1 Gbps)+ * PRETTY_BITS - alias for PRETTY_BITS_METRIC * */ std::string prettyPrint(double val, PrettyType, bool addSpace = true);@@ -525,6 +571,20 @@ const String& input, OutputType& out, const bool ignoreEmpty = false);++/**+ * Split a string into a list of tokens by delimiter with options.+ *+ * Same as split() above but with additional options to control behavior.+ * The SplitOptions allow enabling preallocation which can improve performance+ * when splitting large strings with many expected tokens.+ */+template <class Delim, class String, class OutputType>+std::enable_if_t<detail::IsSplitSupportedContainer<OutputType>::value> split(+ const Delim& delimiter,+ const String& input,+ OutputType& out,+ const SplitOptions& options); /** * split, to an output iterator
@@ -78,6 +78,8 @@ namespace folly { +using detail::linux_syscall;+ namespace detail { SubprocessFdActionsList::SubprocessFdActionsList(@@ -318,8 +320,12 @@ void ProcessReturnCode::enforce(State expected) const { State s = state(); if (s != expected) {- throw std::logic_error(to<std::string>(- "Bad use of ProcessReturnCode; state is ", s, " expected ", expected));+ throw std::logic_error(+ to<std::string>(+ "Bad use of ProcessReturnCode; state is ",+ s,+ " expected ",+ expected)); } } @@ -705,7 +711,7 @@ returnCode_ = ProcessReturnCode::makeRunning(); } -// With -Wclobbered, gcc complains about vfork potentially cloberring the+// With -Wclobbered, gcc complains about vfork potentially clobbering the // childDir variable, even though we only use it on the child side of the // vfork. @@ -912,29 +918,51 @@ #endif // Change effective/real group/user, if requested- if (auto& ptr = args.egid; ptr && 0 != ::setegid(ptr->value)) {- if (auto out = ptr->errout) {+ // Call the raw syscall directly for linux, as glibc set*id() is not+ // safe after vfork() - Linux is the only kernel where libc set*id is not just+ // a call to syscall set*id+ // The risk is that some vfork child is terminated within set*id() with+ // a lock held, deadlocking the next vfork child+ auto idval = [](auto id) { return id ? &id->value : nullptr; };+#if defined(__linux__)+ constexpr auto k_sys_setresgid = SYS_setresgid;+ constexpr auto k_sys_setresuid = SYS_setresuid;+#else+ // Unused+ constexpr auto k_sys_setresgid = -1;+ constexpr auto k_sys_setresuid = -1;+#endif+ if (auto p = idval(args.egid); kIsLinux+ ? p && 0 != linux_syscall(k_sys_setresgid, -1, *p, -1)+ : p && 0 != ::setegid(*p)) {+ if (auto out = args.egid->errout) { *out = errno; } else { return errno; } }- if (auto& ptr = args.gid; ptr && 0 != ::setgid(ptr->value)) {- if (auto out = ptr->errout) {+ if (auto p = idval(args.gid); kIsLinux+ ? p && 0 != linux_syscall(SYS_setgid, *p)+ : p && 0 != ::setgid(*p)) {+ if (auto out = args.gid->errout) { *out = errno; } else { return errno; } }- if (auto& ptr = args.euid; ptr && 0 != ::seteuid(ptr->value)) {- if (auto out = ptr->errout) {+ if (auto p = idval(args.euid); kIsLinux+ ? p && 0 != linux_syscall(k_sys_setresuid, -1, *p, -1)+ : p && 0 != ::seteuid(*p)) {+ if (auto out = args.euid->errout) { *out = errno; } else { return errno; } }- if (auto& ptr = args.uid; ptr && 0 != ::setuid(ptr->value)) {- if (auto out = ptr->errout) {+ if (auto p = idval(args.uid); kIsLinux+ ? p && 0 != linux_syscall(SYS_setuid, *p)+ : p && 0 != ::setuid(*p)) {+ if (auto out = args.uid->errout) { *out = errno; } else { return errno;
@@ -476,7 +476,7 @@ * - If you want to automatically clean up the child process when the * Subprocess is destroyed, use killChildOnDestruction() or * terminateChildOnDestruction()- * - If you want to allow the parent process to exit without waiting on thie+ * - If you want to allow the parent process to exit without waiting on the * child, prefer simply leaking the folly::Subprocess object when the * parent process exits. You could exit with _exit(), or you could * explicitly leak the Subprocess using std::unique_ptr::release() or@@ -544,7 +544,7 @@ int* errout{erroutLifetime_.get()}; }; - typedef boost::container::flat_map<int, int> FdMap;+ using FdMap = boost::container::flat_map<int, int>; FdMap fdActions_; bool closeOtherFds_{false}; bool usePath_{false};@@ -659,6 +659,10 @@ * Poll the child's status. If the process is still running, return false. * Otherwise, return true if the process exited with status 0 (success), * or throw CalledProcessError if the process exited with a non-zero status.+ * Note: this should only be called for processes in the RUNNING state. If+ * another trigger has caused the process to register as exited, or the+ * process is yet to start before this is called, this will throw a+ * std::logic_error. */ bool pollChecked(); @@ -862,7 +866,7 @@ Callback& cb_; int fd_; };- typedef gen::StreamSplitter<StreamSplitterCallback> LineSplitter;+ using LineSplitter = gen::StreamSplitter<StreamSplitterCallback>; public: explicit ReadLinesCallback(
@@ -998,8 +998,8 @@ */ template <class T, class Mutex = SharedMutex> struct [[deprecated(- "use Synchronized and explicit lock(), wlock(), or rlock() instead")]] ImplicitSynchronized- : Synchronized<T, Mutex> {+ "use Synchronized and explicit lock(), wlock(), or rlock() instead")]]+ImplicitSynchronized : Synchronized<T, Mutex> { private: using Base = Synchronized<T, Mutex>; @@ -1756,9 +1756,8 @@ #define SYNCHRONIZED_VAR(var) FB_CONCATENATE(SYNCHRONIZED_##var##_, __LINE__) namespace detail {-struct [[deprecated(- "use explicit lock(), wlock(), or rlock() instead")]] SYNCHRONIZED_macro_is_deprecated {-};+struct [[deprecated("use explicit lock(), wlock(), or rlock() instead")]]+SYNCHRONIZED_macro_is_deprecated {}; } // namespace detail /**
@@ -98,7 +98,7 @@ void reset(T* newPtr = nullptr) { tlp_.reset(newPtr); } - typedef typename ThreadLocalPtr<T, Tag, AccessMode>::Accessor Accessor;+ using Accessor = typename ThreadLocalPtr<T, Tag, AccessMode>::Accessor; Accessor accessAllThreads() const { return tlp_.accessAllThreads(); } private:@@ -145,7 +145,7 @@ template <class T, class Tag = void, class AccessMode = void> class ThreadLocalPtr { private:- typedef threadlocal_detail::StaticMeta<Tag, AccessMode> StaticMeta;+ using StaticMeta = threadlocal_detail::StaticMeta<Tag, AccessMode>; using AccessAllThreadsEnabled = Negation<std::is_same<Tag, void>>;
@@ -72,6 +72,39 @@ template <std::size_t I> using index_constant = std::integral_constant<std::size_t, I>; +namespace detail {++template <typename Int>+constexpr Int parse_uic(char const* str) noexcept {+ Int result = 0;+ while (*str) {+ auto const c = *str++;+ if (c >= '0' && c <= '9') {+ result = result * 10 + (c - '0');+ }+ }+ return result;+}++} // namespace detail++inline namespace literals {+inline namespace integral_constant_literals {++/// operator""_uzic+///+/// Evaluates {XYZ}_uzic as index_constant<{XYZ}>.+///+/// mimic: operator""_uzic, p2725r0+template <char... Digits>+constexpr auto operator""_uzic() noexcept {+ constexpr char digits[] = {Digits..., '\0'};+ return index_constant<detail::parse_uic<size_t>(digits)>{};+}++} // namespace integral_constant_literals+} // namespace literals+ /// always_false /// /// A variable template that is always false but requires template arguments to@@ -112,7 +145,9 @@ template <typename Void, typename T> struct require_sizeof_ {- static_assert(always_false<T>, "application of sizeof fails substitution");+ static_assert(+ always_false<T>,+ "application of sizeof fails substitution - most commonly, the type is incomplete"); }; template <typename T> struct require_sizeof_<decltype(void(sizeof(T))), T> {@@ -128,6 +163,11 @@ /// /// Equivalent to sizeof, but with a static_assert enforcing that application of /// sizeof would not fail substitution.+///+/// Application of sizeof fails on the following kinds of types:+/// * function types.+/// * incomplete types, including possibly-cv-qualified void+/// * references to types to which application of sizeof would fail template <typename T> constexpr std::size_t require_sizeof = detail::require_sizeof_<void, T>::size; @@ -937,6 +977,9 @@ template <typename T, typename... Ts> using IsOneOf = StrictDisjunction<std::is_same<T, Ts>...>; +template <typename T, typename... Ts>+inline constexpr bool is_one_of_v = IsOneOf<T, Ts...>::value;+ /* * Complementary type traits for integral comparisons. *@@ -1033,6 +1076,17 @@ namespace folly { +/// is_non_bool_integral_v+///+/// A common need.+template <typename Int>+inline constexpr bool is_non_bool_integral_v =+ !std::is_same_v<bool, std::remove_cv_t<Int>> && std::is_integral_v<Int>;++template <typename Int>+struct is_non_bool_integral //+ : std::bool_constant<is_non_bool_integral_v<Int>> {};+ // Some compilers have signed __int128 and unsigned __int128 types, and some // libraries with some compilers have traits for those types. It's a mess. // Import things into folly and then fill in whatever is missing.@@ -1279,23 +1333,18 @@ inline constexpr type_identity<Out<>> type_list_concat_<Out>; template <- template <typename...>- class Out,- template <typename...>- class In,+ template <typename...> class Out,+ template <typename...> class In, typename... T> inline constexpr auto type_list_concat_<Out, In<T...>> = type_identity<Out<T...>>{}; template <- template <typename...>- class Out,+ template <typename...> class Out, // Allow input lists to come from heterogeneous templates.- template <typename...>- class InA,+ template <typename...> class InA, typename... A,- template <typename...>- class InB,+ template <typename...> class InB, typename... B, typename... Tail> inline constexpr auto type_list_concat_<Out, InA<A...>, InB<B...>, Tail...> =@@ -1416,14 +1465,11 @@ type_identity<Out<V...>>{}; template <- template <auto...>- class Out,+ template <auto...> class Out, // Allow input lists to come from heterogeneous templates.- template <auto...>- class InA,+ template <auto...> class InA, auto... A,- template <auto...>- class InB,+ template <auto...> class InB, auto... B, typename... Tail> inline constexpr auto value_list_concat_<Out, InA<A...>, InB<B...>, Tail...> =@@ -1444,14 +1490,15 @@ namespace detail { template <typename V, typename... T>-constexpr std::size_t type_pack_find_() {- bool eq[] = {std::is_same_v<V, T>..., true};- for (size_t i = 0; i < sizeof...(T); ++i) {- if (eq[i]) {- return i;- }+constexpr bool type_pack_find_a_[sizeof...(T) + 1] = {+ std::is_same_v<V, T>..., true};++constexpr std::size_t type_pack_find_(bool const* eq) {+ size_t i = 0;+ while (!eq[i]) {+ ++i; }- return sizeof...(T);+ return i; } template <typename>@@ -1459,7 +1506,8 @@ template <template <typename...> class List, typename... T> struct type_list_find_<List<T...>> { template <typename V>- static inline constexpr std::size_t apply = type_pack_find_<V, T...>();+ static inline constexpr std::size_t apply =+ type_pack_find_(type_pack_find_a_<V, T...>); }; } // namespace detail@@ -1470,7 +1518,7 @@ /// type, or the size of the pack if there is no such element. template <typename V, typename... T> inline constexpr std::size_t type_pack_find_v =- detail::type_pack_find_<V, T...>();+ detail::type_pack_find_(detail::type_pack_find_a_<V, T...>); /// type_pack_find_t ///
@@ -320,9 +320,6 @@ * @returns a pointer to the `std::exception` held by `*this`, if one is held; * otherwise, returns `nullptr`. */- std::exception* tryGetExceptionObject() noexcept {- return hasException() ? this->e_.get_exception() : nullptr;- } std::exception const* tryGetExceptionObject() const noexcept { return hasException() ? this->e_.get_exception() : nullptr; }@@ -333,10 +330,6 @@ * returns `nullptr`. */ template <class Ex>- Ex* tryGetExceptionObject() noexcept {- return hasException() ? this->e_.template get_exception<Ex>() : nullptr;- }- template <class Ex> Ex const* tryGetExceptionObject() const noexcept { return hasException() ? this->e_.template get_exception<Ex>() : nullptr; }@@ -407,7 +400,7 @@ /* * The value type for the Try */- typedef void element_type;+ using element_type = void; // Construct a Try holding a successful and void result Try() noexcept : hasValue_(true) {}@@ -525,9 +518,6 @@ * @returns a pointer to the `std::exception` held by `*this`, if one is held; * otherwise, returns `nullptr`. */- std::exception* tryGetExceptionObject() noexcept {- return hasException() ? e_.get_exception() : nullptr;- } std::exception const* tryGetExceptionObject() const noexcept { return hasException() ? e_.get_exception() : nullptr; }@@ -537,10 +527,6 @@ * type `From` permits `std::is_convertible<From*, Ex*>`; otherwise, * returns `nullptr`. */- template <class E>- E* tryGetExceptionObject() noexcept {- return hasException() ? e_.get_exception<E>() : nullptr;- } template <class E> E const* tryGetExceptionObject() const noexcept { return hasException() ? e_.get_exception<E>() : nullptr;
@@ -22,53 +22,53 @@ namespace folly { -namespace {--template <class F>-void codePointToUtf8Impl(char32_t cp, F&& f) {+unicode_code_point_utf8 unicode_code_point_to_utf8(char32_t const cp) { // Based on description from http://en.wikipedia.org/wiki/UTF-8. + auto const ret = [](std::initializer_list<char> const ilist) {+ unicode_code_point_utf8 val{to_narrow(ilist.size()), {}};+ std::memcpy(val.data, ilist.begin(), ilist.size());+ return val;+ };+ if (cp <= 0x7f) {- f({+ return ret({ static_cast<char>(cp), }); } else if (cp <= 0x7FF) {- f({+ return ret({ static_cast<char>(0xC0 | (cp >> 6)), static_cast<char>(0x80 | (0x3f & cp)), }); } else if (cp <= 0xFFFF) {- f({+ return ret({ static_cast<char>(0xE0 | (cp >> 12)), static_cast<char>(0x80 | (0x3f & (cp >> 6))), static_cast<char>(0x80 | (0x3f & cp)), }); } else if (cp <= 0x10FFFF) {- f({+ return ret({ static_cast<char>(0xF0 | (cp >> 18)), static_cast<char>(0x80 | (0x3f & (cp >> 12))), static_cast<char>(0x80 | (0x3f & (cp >> 6))), static_cast<char>(0x80 | (0x3f & cp)), });+ } else {+ return {0, {}}; } } -} // namespace+void appendCodePointToUtf8(char32_t const cp, std::string& out) {+ auto const utf8 = unicode_code_point_to_utf8(cp);+ out.append(reinterpret_cast<char const*>(utf8.data), utf8.size);+} -std::string codePointToUtf8(char32_t cp) {+std::string codePointToUtf8(char32_t const cp) { std::string result;- codePointToUtf8Impl(cp, [&](std::initializer_list<char> data) {- result.assign(data.begin(), data.end());- });+ appendCodePointToUtf8(cp, result); return result; } -void appendCodePointToUtf8(char32_t cp, std::string& out) {- codePointToUtf8Impl(cp, [&](std::initializer_list<char> data) {- out.append(data.begin(), data.end());- });-}- char32_t utf8ToCodePoint( const unsigned char*& p, const unsigned char* const e, bool skipOnError) { // clang-format off@@ -143,8 +143,9 @@ if (skipOnError) { return skip(); }- throw std::runtime_error(to<std::string>(- "folly::utf8ToCodePoint i=", i, " tmp=", (uint32_t)tmp));+ throw std::runtime_error(+ to<std::string>(+ "folly::utf8ToCodePoint i=", i, " tmp=", (uint32_t)tmp)); } // gradually fill a 32 bit integer d with non control bits in tmp
@@ -18,6 +18,7 @@ #pragma once +#include <cstddef> #include <cstdint> #include <stdexcept> #include <string>@@ -75,13 +76,19 @@ ////////////////////////////////////////////////////////////////////// +struct unicode_code_point_utf8 {+ uint32_t size;+ uint8_t data[4];+};+unicode_code_point_utf8 unicode_code_point_to_utf8(char32_t cp);+ /* * Encode a single Unicode code point into a UTF-8 byte sequence. * * Result is undefined if `cp' is an invalid code point. */-std::string codePointToUtf8(char32_t cp); void appendCodePointToUtf8(char32_t cp, std::string& out);+std::string codePointToUtf8(char32_t cp); /* * Decode a single Unicode code point from UTF-8 byte sequence.
@@ -58,23 +58,23 @@ if (hasAuthority_) { toAppend(scheme_, "://", &str); if (!password_.empty()) {- toAppend(username_, ":", password_, "@", &str);+ toAppend(username_, ':', password_, '@', &str); } else if (!username_.empty()) {- toAppend(username_, "@", &str);+ toAppend(username_, '@', &str); } toAppend(host_, &str); if (port_ != 0) {- toAppend(":", port_, &str);+ toAppend(':', port_, &str); } } else {- toAppend(scheme_, ":", &str);+ toAppend(scheme_, ':', &str); } toAppend(path_, &str); if (!query_.empty()) {- toAppend("?", query_, &str);+ toAppend('?', query_, &str); } if (!fragment_.empty()) {- toAppend("#", fragment_, &str);+ toAppend('#', fragment_, &str); } return str; }
@@ -32,7 +32,7 @@ } // namespace -// private default contructor+// private default constructor Uri::Uri() : hasAuthority_(false), port_(0) {} // public string constructor
@@ -355,26 +355,30 @@ } }; +template <typename T>+constexpr bool is_inheritable_v_ = //+ (std::is_class_v<T> || std::is_union_v<T>) &&+ !(std::is_abstract_v<T> || std::is_final_v<T>);+ template <bool> struct inheritable_; template <> struct inheritable_<false> { template <typename T>- using apply = inheritable_inherit_<T>;+ using apply = inheritable_contain_<T>; }; template <> struct inheritable_<true> { template <typename T>- using apply = inheritable_contain_<T>;+ using apply = inheritable_inherit_<T>; }; // inheritable // // A class wrapping an arbitrary type T which is always inheritable, and which // enables empty-base-optimization when possible.-template <typename T>-using inheritable =- typename inheritable_<std::is_final<T>::value>::template apply<T>;+template <typename T, bool C = is_inheritable_v_<T>>+using inheritable = typename inheritable_<C>::template apply<T>; } // namespace detail @@ -435,7 +439,7 @@ /// May be invoked with any arguments. Returns void. struct variadic_noop_fn { template <typename... A>- constexpr void operator()(A&&...) const noexcept {}+ constexpr void operator()(A&&... /*unused*/) const noexcept {} }; inline constexpr variadic_noop_fn variadic_noop; @@ -450,7 +454,7 @@ using value_type = decltype(Value); static inline constexpr value_type value = Value; template <typename... A>- constexpr value_type operator()(A&&...) const noexcept {+ constexpr value_type operator()(A&&... /*unused*/) const noexcept { return value; } };@@ -497,6 +501,8 @@ FOLLY_MSVC_DISABLE_WARNING(4701) // Potentially uninitialized local pointer variable 'uninit' used FOLLY_MSVC_DISABLE_WARNING(4703)+ // Using uninitialized memory `uninit`+ FOLLY_MSVC_DISABLE_WARNING(6001) FOLLY_GNU_DISABLE_WARNING("-Wuninitialized") // Clang doesn't implement -Wmaybe-uninitialized and warns about it FOLLY_GCC_DISABLE_WARNING("-Wmaybe-uninitialized")@@ -549,7 +555,7 @@ // be implementation-defined behavior and that is typically to be avoided; // the following code optimized into the same thing, though constexpr auto m = static_cast<T>(std::numeric_limits<S>::max());- return m < t ? -static_cast<S>(~t) + S{-1} : static_cast<S>(t);+ return static_cast<S>(m < t ? -static_cast<S>(~t) + S{-1} : t); } }; inline constexpr to_signed_fn to_signed{};@@ -751,6 +757,8 @@ private: static_assert(std::is_same<F, decay_t<F>>::value, "mismatch"); + using base = inheritable<F>;+ template <typename R> using result_t = detected_t<invocable_to_detect, R>; template <typename R>@@ -771,6 +779,11 @@ static_assert(std::is_same<TMR, result_t<FMR>>::value, "mismatch"); static_assert(std::is_same<TCR, result_t<FCR>>::value, "mismatch"); + using BML = base&;+ using BCL = base const&;+ using BMR = base&&;+ using BCR = base const&&;+ public: template <typename G, std::enable_if_t<constructible_v<G&&>, int> = 0> FOLLY_ERASE explicit constexpr invocable_to_convertible(G&& g) noexcept(@@ -779,19 +792,19 @@ template <typename..., typename R = FML, if_invocable_as_v<R> = 0> FOLLY_ERASE constexpr operator TML() & noexcept(nx_v<R>) {- return static_cast<FML>(*this)();+ return static_cast<FML>(static_cast<BML>(*this))(); } template <typename..., typename R = FCL, if_invocable_as_v<R> = 0> FOLLY_ERASE constexpr operator TCL() const& noexcept(nx_v<R>) {- return static_cast<FCL>(*this)();+ return static_cast<FCL>(static_cast<BCL>(*this))(); } template <typename..., typename R = FMR, if_invocable_as_v<R> = 0> FOLLY_ERASE constexpr operator TMR() && noexcept(nx_v<R>) {- return static_cast<FMR>(*this)();+ return static_cast<FMR>(static_cast<BMR>(*this))(); } template <typename..., typename R = FCR, if_invocable_as_v<R> = 0> FOLLY_ERASE constexpr operator TCR() const&& noexcept(nx_v<R>) {- return static_cast<FCR>(*this)();+ return static_cast<FCR>(static_cast<BCR>(*this))(); } }; } // namespace detail@@ -857,6 +870,59 @@ } }; inline constexpr invocable_to_fn invocable_to{};++/// object_from_member+/// object_from_member_fn+///+/// Returns the object containing the given field.+///+/// Similar to container_of (linux kernel).+///+/// Example:+///+/// using obj_t = std::pair<int, float>;+/// obj_t obj = {1, 3.0};+/// assert(&obj == object_from_member(obj_t::second, &obj.second);+struct object_from_member_fn {+ private:+ template <typename M, typename O>+ using ptr_t = M O::*;++ template <typename M, typename O>+ static std::ptrdiff_t off(ptr_t<M, O> const p) noexcept {+ O* o = nullptr;+ M* m = &(o->*p);+ return reinterpret_cast<char*>(m) - reinterpret_cast<char*>(o);+ }++ public:+ template <typename M, typename O>+ O* operator()(ptr_t<M, O> const p, M* m) const noexcept {+ return reinterpret_cast<O*>(reinterpret_cast<char*>(m) - off(p));+ }+ template <typename M, typename O>+ O const* operator()(ptr_t<M, O> const p, M const* m) const noexcept {+ return reinterpret_cast<O*>(reinterpret_cast<char const*>(m) - off(p));+ }++ template <typename M, typename O>+ O& operator()(ptr_t<M, O> const p, M& m) const noexcept {+ return *operator()(p, &m);+ }+ template <typename M, typename O>+ O const& operator()(ptr_t<M, O> const p, M const& m) const noexcept {+ return *operator()(p, &m);+ }+ template <typename M, typename O>+ O&& operator()(ptr_t<M, O> const p, M&& m) const noexcept {+ return static_cast<O&&>(*operator()(p, &m));+ }+ template <typename M, typename O>+ O const&& operator()(ptr_t<M, O> const p, M const& m) const noexcept {+ return static_cast<O const&&>(*operator()(p, &m));+ }+};+inline constexpr object_from_member_fn object_from_member{}; #define FOLLY_DETAIL_FORWARD_BODY(...) \ noexcept(noexcept(__VA_ARGS__))->decltype(__VA_ARGS__) { \
@@ -14,4 +14,4 @@ * limitations under the License. */ -#include <folly/executors/VirtualExecutor.h>+#include <folly/executors/VirtualExecutor.h> // @shim
@@ -55,7 +55,7 @@ } // All the requirements to call contains(haystack, needle);-// * both are simd friendly (contigious range, primitive types)+// * both are simd friendly (contiguous range, primitive types) // * integrals only // * needle can be converted to the value_type of haystack and // the result of equality comparison will be the same.
@@ -37,7 +37,7 @@ * register: [true, true, false, false, false, false, false, true] * indexes [0, 1, 2, 3, 4, 5, 6, 7 ] *- * ignore_extema{.first = 1, .last = 2}+ * ignore_extrema{.first = 1, .last = 2} * means that elements with indexes 0, 6, and 7 will be ignored * (w/e that means for an operation) */
@@ -64,7 +64,7 @@ * simdAnyOf<Platform, unrolling = 4>(f, l, p); * * Like std::any_of but with vectorized predicates.- * Predicate shoud accept Platform::reg_t and return Platform::logical_t.+ * Predicate should accept Platform::reg_t and return Platform::logical_t. * * By default is unrolled 4 ways but for expensive predicates you might want to * use an unroll factor of 1.
@@ -79,7 +79,7 @@ /** * SimdForEachMainLoop *- * Implementaiton detail of simdForEach+ * Implementation detail of simdForEach * * Regardless of how you chose to handle tails, the middle will be the same. * The operator() returns true if the delegate returned to break.@@ -95,8 +95,9 @@ FOLLY_ALWAYS_INLINE bool operator()( int cardinal, T*& f, T* l, Delegate& delegate, index_constant<1>) const { while (f != l) {- if (delegate.step(f, ignore_none{}, index_constant<0>{}))+ if (delegate.step(f, ignore_none{}, index_constant<0>{})) { return true;+ } f += cardinal; } @@ -113,8 +114,9 @@ template <std::size_t i> FOLLY_ALWAYS_INLINE bool operator()(index_constant<i> unrollI) {- if (f == l)+ if (f == l) { return true;+ } shouldBreak = delegate.step(f, ignore_none{}, unrollI); f += cardinal;
@@ -54,7 +54,7 @@ template <typename Platform> struct SimdPlatformCommon { /**- * sclar_t - type of scalar we operate on (uint8_t, uint16_t etc)+ * scalar_t - type of scalar we operate on (uint8_t, uint16_t etc) * reg_t - type of a simd register (__m128i) * logical_t - type of a simd logical register (matches reg_t so far) **/@@ -281,7 +281,7 @@ FOLLY_ERASE static bool any(logical_t log) { return movemask<scalar_t>(log).first; } -#if 0 // disabled untill we have a test where this is relevant+#if 0 // disabled until we have a test where this is relevant FOLLY_ERASE static bool all(logical_t log) { auto [bits, bitsPerElement] = movemask<scalar_t>(log);@@ -360,7 +360,7 @@ FOLLY_ERASE static bool any(logical_t log) { return simd::movemask<scalar_t>(log).first; } -#if 0 // disabled untill we have a test where this is relevant+#if 0 // disabled until we have a test where this is relevant FOLLY_ERASE static bool all(logical_t log) { auto [bits, bitsPerElement] = movemask<scalar_t>(log);@@ -480,7 +480,7 @@ return vgetq_lane_u64(u64, 0); } -#if 0 // disabled untill we have a test where this is relevant+#if 0 // disabled until we have a test where this is relevant FOLLY_ERASE static bool all(logical_t log) { // Not quite what they did in .Net runtime, but
@@ -42,7 +42,7 @@ * return an array of results. */ template <typename T, std::size_t N, typename Op>- FOLLY_NODISCARD FOLLY_ALWAYS_INLINE static constexpr auto arrayMap(+ [[nodiscard]] FOLLY_ALWAYS_INLINE static constexpr auto arrayMap( const std::array<T, N>& x, Op op) { return arrayMapImpl(x, op, std::make_index_sequence<N>()); }@@ -56,7 +56,7 @@ * (a + b) + (c + d) */ template <typename T, std::size_t N, typename Op>- FOLLY_NODISCARD FOLLY_ALWAYS_INLINE static constexpr T arrayReduce(+ [[nodiscard]] FOLLY_ALWAYS_INLINE static constexpr T arrayReduce( const std::array<T, N>& x, Op op) { return arrayReduceImpl<0, N>(x, op); }@@ -64,11 +64,10 @@ /** * unrollUntil<N>(op) *- * Do operation N times or until it returns true to break.- * Op accepts integral_constant<i> so it can keep track of a step begin- * executed.+ * Do operation N times or until it returns true to break.+ * Op accepts index_constant so it can keep track of a step begin executed. *- * Returns wether true if it was interrupted (you can know if the op breaked)+ * Returns whether true if it was interrupted (you can know if the op broke) */ template <std::size_t N, typename Op> FOLLY_ALWAYS_INLINE static constexpr bool unrollUntil(Op op) {@@ -78,7 +77,7 @@ private: template <typename T, std::size_t N, typename Op, std::size_t... i> FOLLY_ALWAYS_INLINE static constexpr auto arrayMapImpl(- const std::array<T, N>& x, Op op, std::index_sequence<i...>) {+ const std::array<T, N>& x, Op op, std::index_sequence<i...> /*unused*/) { using U = decltype(op(std::declval<const T&>())); FOLLY_PUSH_WARNING@@ -98,7 +97,7 @@ std::size_t N, typename Op> FOLLY_ALWAYS_INLINE static constexpr std::enable_if_t<l - f == 1, T>- arrayReduceImpl(std::array<T, N> const& x, Op) {+ arrayReduceImpl(std::array<T, N> const& x, Op /*unused*/) { return x[f]; } @@ -118,7 +117,7 @@ template <typename Op, std::size_t... i> FOLLY_ALWAYS_INLINE static constexpr bool unrollUntilImpl(- Op op, std::index_sequence<i...>) {+ Op op, std::index_sequence<i...> /*unused*/) { return (... || op(index_constant<i>{})); } };
@@ -64,6 +64,7 @@ inline auto base64Decode(std::string_view s) -> std::string; inline auto base64URLEncode(std::string_view s) -> std::string; inline auto base64URLDecode(std::string_view s) -> std::string;+inline auto base64PHPStrictDecode(std::string_view s) -> std::string; // Low level API. //@@ -100,6 +101,11 @@ const char* f, const char* l) noexcept; constexpr std::size_t base64URLDecodedSize(std::string_view s) noexcept; +constexpr std::size_t base64PHPStrictDecodeRequiredOutputSize(+ const char* f, const char* l) noexcept;+constexpr std::size_t base64PHPStrictDecodeRequiredOutputSize(+ std::string_view s) noexcept;+ struct base64_decode_result { bool is_success; char* o;@@ -125,6 +131,11 @@ inline base64_decode_result base64URLDecodeRuntime( std::string_view s, char* o) noexcept; +inline constexpr base64_decode_result base64PHPStrictDecode(+ const char* f, const char* l, char* o) noexcept;+inline constexpr base64_decode_result base64PHPStrictDecode(+ std::string_view s, char* o) noexcept;+ // ----------------------------------------------------------------- // implementation @@ -193,6 +204,17 @@ return folly::base64URLDecodedSize(s.data(), s.data() + s.size()); } +constexpr std::size_t base64PHPStrictDecodeRequiredOutputSize(+ const char* f, const char* l) noexcept {+ return detail::base64_detail::base64PHPStrictDecodeRequiredOutputSize(f, l);+}++constexpr std::size_t base64PHPStrictDecodeRequiredOutputSize(+ std::string_view s) noexcept {+ return folly::base64PHPStrictDecodeRequiredOutputSize(+ s.data(), s.data() + s.size());+}+ inline constexpr base64_decode_result base64Decode( const char* f, const char* l, char* o) noexcept { auto detailResult = detail::base64_detail::base64Decode(f, l, o);@@ -237,6 +259,17 @@ return folly::base64URLDecodeRuntime(s.data(), s.data() + s.size(), o); } +inline constexpr base64_decode_result base64PHPStrictDecode(+ const char* f, const char* l, char* o) noexcept {+ auto detailResult = detail::base64_detail::base64PHPStrictDecode(f, l, o);+ return {detailResult.isSuccess, detailResult.o};+}++inline constexpr base64_decode_result base64PHPStrictDecode(+ std::string_view s, char* o) noexcept {+ return folly::base64PHPStrictDecode(s.data(), s.data() + s.size(), o);+}+ // NOTE: for resizeWithoutInitialization we don't need to declare the macros, // since we are using char which is already included by default. inline std::string base64Decode(std::string_view s) {@@ -261,15 +294,24 @@ return res; } -inline bool isBase64URL(std::string_view s) {+inline std::string base64PHPStrictDecode(std::string_view s) { std::string res;- std::size_t resSize = folly::base64URLDecodedSize(s);- folly::resizeWithoutInitialization(res, resSize);+ folly::resizeWithoutInitialization(+ res, base64PHPStrictDecodeRequiredOutputSize(s)); - if (!folly::base64URLDecodeRuntime(s, res.data()).is_success) {- return false;+ if (s.empty()) {+ return res; }- return true;++ base64_decode_result decoded = folly::base64PHPStrictDecode(s, res.data());++ if (!decoded.is_success) {+ folly::throw_exception<base64_decode_error>("Base64PHP Decoding failed");+ }++ res.resize(decoded.o - res.data());+ res.shrink_to_fit();+ return res; } } // namespace folly
@@ -84,7 +84,8 @@ }); } - bool await_suspend(folly::coro::coroutine_handle<> awaitingCoroutine) {+ bool await_suspend(+ folly::coro::coroutine_handle<> awaitingCoroutine) noexcept { return state_.withWLock([&](State& state) { if (state.cancelled || !state.receiver || !receiverWait(*state.receiver, this)) {@@ -105,12 +106,12 @@ return std::move(result.value()); } - // FIXME: The default implementation of `co_await_result` will convert the- // `getResult()` branch below that returns an empty `Try` into an error- // state. That's because the normal `folly::coro` contract does not allow- // producing an empty `Try`. Therefore, any future `await_resume_result()`- // implementation should investigate the intended semantics of this- // `getResult()` logic, and provide better handling:+ // FIXME: The default implementation of `value_or_error_or_stopped` will+ // convert the `getResult()` branch below that returns an empty `Try` into an+ // error state. That's because the normal `folly::coro` contract does not+ // allow producing an empty `Try`. Therefore, any future+ // `await_resume_result()` implementation should investigate the intended+ // semantics of this `getResult()` logic, and provide better handling: // // if (!state.receiver) { // return Try<TValue>();
@@ -37,8 +37,9 @@ std::shared_ptr<folly::channels::RateLimiter> rateLimiter, MergeChannel<KeyType, Unit> mergeChannel, Receiver<MergeChannelEvent<KeyType, Unit>> mergeChannelReceiver)- : implState_(make_intrusive<ImplState>(- std::move(executors), std::move(rateLimiter))),+ : implState_(+ make_intrusive<ImplState>(+ std::move(executors), std::move(rateLimiter))), channels_(std::move(mergeChannel)), handle_(consumeChannelWithCallback( std::move(mergeChannelReceiver),@@ -118,8 +119,7 @@ template < typename Function,- typename ReturnType =- typename std::invoke_result_t<Function>::StorageType>+ typename ReturnType = typename invoke_result_t<Function>::StorageType> static folly::coro::Task<ReturnType> catchNonCoroException(Function func) { auto result = folly::makeTryWith(std::move(func)); if (result.hasException()) {
@@ -146,8 +146,9 @@ CHECK_EQ(getReceiverState(), ChannelState::CancellationTriggered); receiver_ = nullptr; if (fromHandleDestruction) {- co_await callCallback(Try<TValue>(- folly::make_exception_wrapper<folly::OperationCancelled>()));+ co_await callCallback(+ Try<TValue>(+ folly::make_exception_wrapper<folly::OperationCancelled>())); } maybeDelete(); }
@@ -86,8 +86,9 @@ CHECK(unbufferedInputReceiver != nullptr) << "The bridge in the input receiver is null."; CHECK(buffers- .insert(std::make_pair(- unbufferedInputReceiver.get(), std::move(buffer)))+ .insert(+ std::make_pair(+ unbufferedInputReceiver.get(), std::move(buffer))) .second); receivers_.insert(unbufferedInputReceiver.release()); }
@@ -196,16 +196,18 @@ auto keyToRemove = existingReceiverIt->first; state->receivers[existingReceiverIt->second] = nullptr; state->receiversByKey.erase(existingReceiverIt);- state->sender->senderPush(MergeChannelEvent<KeyType, ValueType>{- keyToRemove, MergeChannelReceiverRemoved{}});+ state->sender->senderPush(+ MergeChannelEvent<KeyType, ValueType>{+ keyToRemove, MergeChannelReceiverRemoved{}}); } auto [it, _] = state->receiversByKey.insert( std::make_pair(key, unbufferedReceiver.get())); auto* receiverPtr = unbufferedReceiver.get(); state->receivers.insert( std::make_pair(unbufferedReceiver.release(), &it->first));- state->sender->senderPush(MergeChannelEvent<KeyType, ValueType>{- key, MergeChannelReceiverAdded{}});+ state->sender->senderPush(+ MergeChannelEvent<KeyType, ValueType>{+ key, MergeChannelReceiverAdded{}}); processAllAvailableValues(state, receiverPtr, std::move(buffer)); } @@ -228,8 +230,9 @@ auto keyToRemove = receiverIt->first; state->receivers[receiverIt->second] = nullptr; state->receiversByKey.erase(receiverIt);- state->sender->senderPush(MergeChannelEvent<KeyType, ValueType>{- keyToRemove, MergeChannelReceiverRemoved{}});+ state->sender->senderPush(+ MergeChannelEvent<KeyType, ValueType>{+ keyToRemove, MergeChannelReceiverRemoved{}}); } folly::F14FastSet<KeyType> getReceiverKeys() {@@ -349,8 +352,9 @@ if (inputResult.hasValue()) { // We have received a normal value from an input receiver. Write it to // the output receiver.- state->sender->senderPush(MergeChannelEvent<KeyType, ValueType>{- *key, std::move(inputResult.value())});+ state->sender->senderPush(+ MergeChannelEvent<KeyType, ValueType>{+ *key, std::move(inputResult.value())}); } else { // The input receiver was closed. return inputResult.hasException()@@ -374,12 +378,13 @@ auto keyToRemove = *key; CHECK_EQ(state->receiversByKey.erase(keyToRemove), 1); if (state->getSenderState() == ChannelState::Active) {- state->sender->senderPush(MergeChannelEvent<KeyType, ValueType>{- keyToRemove,- MergeChannelReceiverClosed{- closeResult.exception.has_value()- ? std::move(closeResult.exception.value())- : exception_wrapper()}});+ state->sender->senderPush(+ MergeChannelEvent<KeyType, ValueType>{+ keyToRemove,+ MergeChannelReceiverClosed{+ closeResult.exception.has_value()+ ? std::move(closeResult.exception.value())+ : exception_wrapper()}}); } } state->receivers.erase(receiver);@@ -414,8 +419,9 @@ if (state->getSenderState() == ChannelState::Active) { for (auto [key, receiver] : state->receiversByKey) { state->receivers[receiver] = nullptr;- state->sender->senderPush(MergeChannelEvent<KeyType, ValueType>{- key, MergeChannelReceiverRemoved{}});+ state->sender->senderPush(+ MergeChannelEvent<KeyType, ValueType>{+ key, MergeChannelReceiverRemoved{}}); } if (closeResult.exception.has_value()) { state->sender->senderClose(std::move(closeResult.exception.value()));
@@ -185,7 +185,7 @@ void start(Receiver<InputValueType> inputReceiver) { executeWithMutexWhenReady( [this, inputReceiver = std::move(inputReceiver)]() mutable- -> folly::coro::Task<void> {+ -> folly::coro::Task<void> { co_await processStart(std::move(inputReceiver)); }); }@@ -225,7 +225,7 @@ void destroyHandle(CloseResult closeResult) { executeWithMutexWhenReady( [this, closeResult = std::move(closeResult)]() mutable- -> folly::coro::Task<void> {+ -> folly::coro::Task<void> { co_await processHandleDestroyed(std::move(closeResult)); }); }@@ -387,8 +387,9 @@ for (auto it = subscriptions_.begin(); it != subscriptions_.end();) { auto& sender = std::get<FanoutSender<OutputValueType>>(it->second); if (!sender.anySubscribers()) {- clearedSubscriptions.push_back(std::make_pair(- it->first, std::move(std::get<KeyContextType>(it->second))));+ clearedSubscriptions.push_back(+ std::make_pair(+ it->first, std::move(std::get<KeyContextType>(it->second)))); it = subscriptions_.erase(it); subscriptionsToRemove++; } else {
@@ -54,6 +54,8 @@ public: explicit ProxyChannel(detail::ProxyChannelProcessor<ValueType>* processor);+ ProxyChannel(const ProxyChannel&) = delete;+ ProxyChannel& operator=(const ProxyChannel&) = delete; ProxyChannel(ProxyChannel&& other) noexcept; ProxyChannel& operator=(ProxyChannel&& other) noexcept; ~ProxyChannel();
@@ -430,7 +430,7 @@ // user's initialization function and resume the resumableTransform. auto* reinitializeEx = closeResult.exception- ->get_exception<ReinitializeException<InitializeArg>>();+ ->get_mutable_exception<ReinitializeException<InitializeArg>>(); co_await initializeImpl(std::move(reinitializeEx->initializeArg)); co_return; }
@@ -84,13 +84,14 @@ private: void ensureCorrectType(bool secondType) const { if (secondType != !!(storage_ & kTypeMask)) {- throw std::runtime_error(fmt::format(- "Incorrect type specified. Given: {}, Stored: {}",- secondType ? folly::demangle(typeid(SecondType).name())- : folly::demangle(typeid(FirstType).name()),- storage_ & kTypeMask- ? folly::demangle(typeid(SecondType).name())- : folly::demangle(typeid(FirstType).name())));+ throw std::runtime_error(+ fmt::format(+ "Incorrect type specified. Given: {}, Stored: {}",+ secondType ? folly::demangle(typeid(SecondType).name())+ : folly::demangle(typeid(FirstType).name()),+ storage_ & kTypeMask+ ? folly::demangle(typeid(SecondType).name())+ : folly::demangle(typeid(FirstType).name()))); } }
@@ -172,7 +172,7 @@ void consume(ChannelBridgeBase*) override { cancelSource_.requestCancellation(); executor_->add([this]() {- CHECK(!callbackToFire_.second.isReady());+ CHECK(!callbackToFire_.first.isFulfilled()); callbackToFire_.first.setValue(CallbackToFire::Consume); }); }@@ -184,7 +184,7 @@ void canceled(ChannelBridgeBase*) override { cancelSource_.requestCancellation(); executor_->add([this]() {- CHECK(!callbackToFire_.second.isReady());+ CHECK(!callbackToFire_.first.isFulfilled()); callbackToFire_.first.setValue(CallbackToFire::Canceled); }); }
@@ -71,7 +71,7 @@ * fits in the range of numbers representable by time_t. * * This is similar to the normal folly::tryTo() behavior when converting- * arthmetic types to an integer type, except that it does not complain about+ * arithmetic types to an integer type, except that it does not complain about * floating point conversions losing precision. */ template <typename Src>@@ -87,7 +87,7 @@ // 18446744073709551615 <- time_t max (when time_t is int64_t) // 18446744073709551616 <- next representable float or double. // The floating point value that gets chosen depends on the floating point- // implementation. IEEE arthimetic rounds to nearest.+ // implementation. IEEE arithmetic rounds to nearest. static_assert( std::numeric_limits<Src>::round_style == std::round_to_nearest, "This function is only implemented for IEEE round to nearest. Please update it if you need other round styles.");@@ -523,12 +523,12 @@ // Perform the division as an integer, and check that the result fits in // the output integer type auto outputValue = (seconds / Tgt::period::num);- auto expectedOuput = tryTo<typename Tgt::rep>(outputValue);- if (expectedOuput.hasError()) {- return makeUnexpected(expectedOuput.error());+ auto expectedOutput = tryTo<typename Tgt::rep>(outputValue);+ if (expectedOutput.hasError()) {+ return makeUnexpected(expectedOutput.error()); } - return Tgt{expectedOuput.value()};+ return Tgt{expectedOutput.value()}; } }
@@ -331,25 +331,39 @@ if (info.positionalOptions) { parser = parser.positional(*info.positionalOptions); }+ try {+ auto cmdOptions = parser.run(); - auto cmdOptions = parser.run();+ po::store(cmdOptions, vm);+ po::notify(vm); - po::store(cmdOptions, vm);- po::notify(vm);+ // If positional arguments are specified they should get mapped to a named+ // arg and don't need to be double collected+ auto cmdArgs = po::collect_unrecognized(+ cmdOptions.options,+ info.positionalOptions+ ? po::exclude_positional+ : po::include_positional); - // If positional arguments are specified they should get mapped to a named arg- // and don't need to be double collected- auto cmdArgs = po::collect_unrecognized(- cmdOptions.options,- info.positionalOptions ? po::exclude_positional : po::include_positional);+ cmdArgs.insert(cmdArgs.end(), endArgs.begin(), endArgs.end()); - cmdArgs.insert(cmdArgs.end(), endArgs.begin(), endArgs.end());+ for (const auto& callback : callbackFunctions_) {+ callback(cmd, vm, cmdArgs);+ } - for (const auto& callback : callbackFunctions_) {- callback(cmd, vm, cmdArgs);+ info.command(vm, cmdArgs);+ } catch (const po::required_option& ex) {+ // The top run() function won't be able to capture the command name.+ // We include both program name and command name in the error message for+ // better UX.+ throw ProgramExit(+ 1,+ fmt::format(+ "Missing required option: '{}'. Run '{} {} --help' for help.",+ ex.get_option_name(),+ programName_,+ cmd)); }-- info.command(vm, cmdArgs); } bool NestedCommandLineApp::isBuiltinCommand(const std::string& name) const {
@@ -117,11 +117,18 @@ return globalOptions_; } - /*+ /** * Return the commands map, so you can see the registered commands and get * access to their respective options descriptions. */- const std::map<std::string, CommandInfo>& commands() { return commands_; }+ const std::map<std::string, CommandInfo>& commands() const {+ return commands_;+ }++ /**+ * Return the aliases map, so you can see the registered aliases.+ */+ const std::map<std::string, std::string>& aliases() const { return aliases_; } /** * Add a command.
@@ -232,14 +232,13 @@ // clang-format on } -typedef void (*FlagAdder)(+using FlagAdder = void (*)( folly::gflags::CommandLineFlagInfo&&, po::options_description&, ProgramOptionsStyle); const std::unordered_map<std::string, FlagAdder> gFlagAdders = {-#define X(NAME, TYPE) \- { NAME, addGFlag<TYPE> }+#define X(NAME, TYPE) {NAME, addGFlag<TYPE>} X("bool", bool), X("int32", int32_t), X("int64", int64_t),
@@ -62,7 +62,6 @@ #include <folly/compression/Utils.h> #include <folly/io/Cursor.h> #include <folly/lang/Bits.h>-#include <folly/stop_watch.h> using folly::compression::detail::dataStartsWithLE; using folly::compression::detail::prefixToStringLE;@@ -221,8 +220,9 @@ void StreamCodec::assertStateIs(State expected) const { if (state_ != expected) {- throw std::logic_error(folly::to<std::string>(- "Codec: state is ", state_, "; expected state ", expected));+ throw std::logic_error(+ folly::to<std::string>(+ "Codec: state is ", state_, "; expected state ", expected)); } } @@ -1188,8 +1188,9 @@ lzma_ret const rc = lzma_easy_encoder(cstream_.get_pointer(), level_, LZMA_CHECK_NONE); if (rc != LZMA_OK) {- throw std::runtime_error(folly::to<std::string>(- "LZMA2StreamCodec: lzma_easy_encoder error: ", rc));+ throw std::runtime_error(+ folly::to<std::string>(+ "LZMA2StreamCodec: lzma_easy_encoder error: ", rc)); } } @@ -1200,8 +1201,9 @@ lzma_ret const rc = lzma_auto_decoder( dstream_.get_pointer(), std::numeric_limits<uint64_t>::max(), 0); if (rc != LZMA_OK) {- throw std::runtime_error(folly::to<std::string>(- "LZMA2StreamCodec: lzma_auto_decoder error: ", rc));+ throw std::runtime_error(+ folly::to<std::string>(+ "LZMA2StreamCodec: lzma_auto_decoder error: ", rc)); } } @@ -1814,21 +1816,25 @@ } // Check that none of the codecs are null- DCHECK(std::none_of(- codecs_.begin(), codecs_.end(), [](std::unique_ptr<Codec> const& codec) {- return codec == nullptr;- }));+ DCHECK(+ std::none_of(+ codecs_.begin(),+ codecs_.end(),+ [](std::unique_ptr<Codec> const& codec) {+ return codec == nullptr;+ })); // Check that the terminal codec's type is not duplicated (with the exception // of USER_DEFINED). if (terminalCodec_) {- DCHECK(std::none_of(- codecs_.begin(),- codecs_.end(),- [&](std::unique_ptr<Codec> const& codec) {- return codec->type() != CodecType::USER_DEFINED &&- codec->type() == terminalCodec_->type();- }));+ DCHECK(+ std::none_of(+ codecs_.begin(),+ codecs_.end(),+ [&](std::unique_ptr<Codec> const& codec) {+ return codec->type() != CodecType::USER_DEFINED &&+ codec->type() == terminalCodec_->type();+ })); } bool const terminalNeedsUncompressedLength =
@@ -17,21 +17,69 @@ #pragma once #include <memory>+#include <optional> #include <folly/Memory.h> #include <folly/Synchronized.h> +/**+ * Temporary implementation detail:+ *+ * The compression context pool singletons are actually used during static+ * initialization time by other modules, so those singletons need to avoid+ * SIOF issues. They do that by being marked `constinit`, so that they have+ * already been constructed before static initialization / program execution+ * begins. That means that the context pools need `constexpr` constructors.+ * `std::vector` is supposed to have a `constexpr` constructor in C++20, but+ * we don't seem to have it (everywhere).+ *+ * So while we can't rely on `constexpr std::vector::vector()`, we wrap the+ * vector with a `std::optional`. When we can `constexpr` construct the vector,+ * we do so. Otherwise, we construct it as empty and have to add checks to+ * populate it later.+ *+ * When folly is only being built on platforms that have this, we can remove+ * the `std::optional` shim etc.+ */+#ifndef FOLLY_COMPRESSION_HAS_CONSTEXPR_VECTOR+#if defined(__cpp_lib_constexpr_vector) && \+ __cpp_lib_constexpr_vector >= 201907L && !defined(_MSC_VER)+#define FOLLY_COMPRESSION_HAS_CONSTEXPR_VECTOR 1+#else+#define FOLLY_COMPRESSION_HAS_CONSTEXPR_VECTOR 0+#endif+#endif+ namespace folly { namespace compression { -template <typename T, typename Creator, typename Deleter, typename Resetter>+struct CompressionContextPoolDefaultCallback {+ void operator()() const {}+};++constexpr size_t COMPRESSION_CONTEXT_POOL_CALLBACK_INTERVAL = 1024;++/**+ * This implementation is slow under contention. Except under uncontended+ * scenarios, you shouldn't use it directly. You likely want to use the+ * CompressionCoreLocalContextPool instead, which, behind the fast cache slots,+ * is backed by this implementation.+ */+template <+ typename T,+ typename Creator,+ typename Deleter,+ typename Resetter,+ typename Sizeof,+ typename Callback = CompressionContextPoolDefaultCallback> class CompressionContextPool { private: using InternalRef = std::unique_ptr<T, Deleter>; class ReturnToPoolDeleter { public:- using Pool = CompressionContextPool<T, Creator, Deleter, Resetter>;+ using Pool =+ CompressionContextPool<T, Creator, Deleter, Resetter, Sizeof, Callback>; explicit ReturnToPoolDeleter(Pool* pool) : pool_(pool) { DCHECK(pool); } @@ -48,65 +96,145 @@ using Object = T; using Ref = std::unique_ptr<T, ReturnToPoolDeleter>; - explicit CompressionContextPool(+ constexpr explicit CompressionContextPool( Creator creator = Creator(), Deleter deleter = Deleter(),- Resetter resetter = Resetter())+ Resetter resetter = Resetter(),+ Sizeof size_of = Sizeof(),+ Callback callback = Callback()) : creator_(std::move(creator)), deleter_(std::move(deleter)), resetter_(std::move(resetter)),- stack_(),+ size_of_(std::move(size_of)),+ callback_(std::move(callback)),+ state_(), created_(0) {} Ref get() {- auto stack = stack_.wlock();- if (stack->empty()) {- T* t = creator_();- if (t == nullptr) {- throw_exception<std::bad_alloc>();+ bool do_cb = false;+ Ref ref{nullptr, get_deleter()};+ {+ auto lock = state_.wlock();+#if !FOLLY_COMPRESSION_HAS_CONSTEXPR_VECTOR+ if (!lock->stack_) {+ lock->stack_.emplace(); }- created_++;- return Ref(t, get_deleter());+#endif+ auto& stack = *lock->stack_;+ if (!stack.empty()) {+ auto ptr = std::move(stack.back());+ stack.pop_back();+ do_cb = (lock->callback_counter_++ %+ COMPRESSION_CONTEXT_POOL_CALLBACK_INTERVAL) == 0;+ if (!ptr) {+ throw_exception<std::logic_error>(+ "A nullptr snuck into our context pool!?!?");+ }+ ref = Ref(ptr.release(), get_deleter());+ } }- auto ptr = std::move(stack->back());- stack->pop_back();- if (!ptr) {- throw_exception<std::logic_error>(- "A nullptr snuck into our context pool!?!?");+ if (do_cb) {+ callback_(); }- return Ref(ptr.release(), get_deleter());+ if (!ref) {+ ref = create();+ }+ return ref; } size_t created_count() const { return created_.load(); } - size_t size() { return stack_.rlock()->size(); }+ size_t size() {+ auto lock = state_.rlock();+#if !FOLLY_COMPRESSION_HAS_CONSTEXPR_VECTOR+ if (!lock->stack_) {+ return 0;+ }+#endif+ auto& stack = *lock->stack_;+ return stack.size();+ } + /// @returns the total bytes of memory used by the pool.+ size_t bytes_used() const {+ auto lock = state_.rlock();+#if !FOLLY_COMPRESSION_HAS_CONSTEXPR_VECTOR+ if (!lock->stack_) {+ return 0;+ }+#endif+ auto& stack = *lock->stack_;+ size_t bytes = 0;+ for (auto& ptr : stack) {+ bytes += size_of_(ptr.get());+ }+ return bytes;+ }+ ReturnToPoolDeleter get_deleter() { return ReturnToPoolDeleter(this); } - Resetter& get_resetter() { return resetter_; }+ const Resetter& get_resetter() { return resetter_; } + const Sizeof& get_sizeof() const { return size_of_; }+ void flush_deep() { flush_shallow(); // no backing stack, so deep == shallow } void flush_shallow() {- auto stack = stack_.wlock();- stack->resize(0);+ auto lock = state_.wlock();+#if !FOLLY_COMPRESSION_HAS_CONSTEXPR_VECTOR+ if (!lock->stack_) {+ return;+ }+#endif+ auto& stack = *lock->stack_;+ stack.resize(0); } private: void add(InternalRef ptr) { DCHECK(ptr); resetter_(ptr.get());- stack_.wlock()->push_back(std::move(ptr));+ auto lock = state_.wlock();+ // add() can only be called when we get a ref we created back, so the+ // stack must already have been initialized. So we don't need to check.+ auto& stack = *lock->stack_;+ stack.push_back(std::move(ptr)); } - Creator creator_;- Deleter deleter_;- Resetter resetter_;+ Ref create() {+ T* t = creator_();+ if (t == nullptr) {+ throw_exception<std::bad_alloc>();+ }+ created_++;+ return Ref(t, get_deleter());+ } - folly::Synchronized<std::vector<InternalRef>> stack_;+ const Creator creator_;+ const Deleter deleter_;+ const Resetter resetter_;+ const Sizeof size_of_;+ const Callback callback_;++ struct SyncState {+ explicit constexpr SyncState()+ :+#if FOLLY_COMPRESSION_HAS_CONSTEXPR_VECTOR+ stack_(std::in_place, std::vector<InternalRef>{}),+#else+ stack_(),+#endif+ callback_counter_(0) {+ }++ std::optional<std::vector<InternalRef>> stack_;+ size_t callback_counter_;+ };++ folly::Synchronized<SyncState> state_; std::atomic<size_t> created_; };
@@ -20,6 +20,7 @@ #include <folly/Portability.h> #include <folly/memory/Malloc.h>+#include <folly/system/HardwareConcurrency.h> #ifndef FOLLY_COMPRESSION_USE_HUGEPAGES #if defined(__linux__) && !defined(__ANDROID__)@@ -37,19 +38,55 @@ #ifndef ZSTD_STATIC_LINKING_ONLY #define ZSTD_STATIC_LINKING_ONLY #endif-#include <zstd.h>+#include <zstd.h> // @donotremove #endif +#if FOLLY_HAVE_WEAK_SYMBOLS+FOLLY_ATTR_WEAK double+folly_zstd_cctx_pool_stripes_cpu_multiplier_default() noexcept;+FOLLY_ATTR_WEAK double+folly_zstd_dctx_pool_stripes_cpu_multiplier_default() noexcept;+#else+static double (+ *folly_zstd_cctx_pool_stripes_cpu_multiplier_default)() noexcept = nullptr;+static double (+ *folly_zstd_dctx_pool_stripes_cpu_multiplier_default)() noexcept = nullptr;+#endif++FOLLY_GFLAGS_DEFINE_double(+ folly_zstd_cctx_pool_stripes_cpu_multiplier,+ folly_zstd_cctx_pool_stripes_cpu_multiplier_default+ ? folly_zstd_cctx_pool_stripes_cpu_multiplier_default()+ : 0.0,+ "Number of stripes for compression context pool specified as a multiplier of hardware concurrency.");+FOLLY_GFLAGS_DEFINE_double(+ folly_zstd_dctx_pool_stripes_cpu_multiplier,+ folly_zstd_dctx_pool_stripes_cpu_multiplier_default+ ? folly_zstd_dctx_pool_stripes_cpu_multiplier_default()+ : 0.0,+ "Number of stripes for decompression context pool specified as a multiplier of hardware concurrency.");+ namespace folly { namespace compression { namespace contexts { #if FOLLY_HAVE_LIBZSTD namespace {-// These objects have no static dependencies and therefore no SIOF issues.-ZSTD_CCtx_Pool zstd_cctx_pool_singleton;-ZSTD_DCtx_Pool zstd_dctx_pool_singleton; +// These objects must be constinit in order to be SIOF-safe, since they are+// accessed during the static initialization of other translation units.+struct ZSTD_cctx_pool_singleton : ZSTD_CCtx_Pool {+ constexpr ZSTD_cctx_pool_singleton()+ : ZSTD_CCtx_Pool{8, {}, {}, {}, {}, ZSTD_CCtx_Pool_Callback{this}} {}+};+struct ZSTD_dctx_pool_singleton : ZSTD_DCtx_Pool {+ constexpr ZSTD_dctx_pool_singleton()+ : ZSTD_DCtx_Pool{8, {}, {}, {}, {}, ZSTD_DCtx_Pool_Callback{this}} {}+};++constinit ZSTD_cctx_pool_singleton zstd_cctx_pool_singleton;+constinit ZSTD_dctx_pool_singleton zstd_dctx_pool_singleton;+ #if FOLLY_COMPRESSION_USE_HUGEPAGES constexpr bool use_huge_pages = kIsArchAmd64; @@ -74,11 +111,11 @@ } } -ZSTD_customMem huge_page_custom_mem = (use_huge_pages && usingJEMalloc())+const ZSTD_customMem huge_page_custom_mem = (use_huge_pages && usingJEMalloc()) ? (ZSTD_customMem){huge_page_alloc, huge_page_free, nullptr} : ZSTD_defaultCMem; #else-ZSTD_customMem huge_page_custom_mem = ZSTD_defaultCMem;+const ZSTD_customMem huge_page_custom_mem = ZSTD_defaultCMem; #endif } // anonymous namespace@@ -109,6 +146,40 @@ size_t const err = ZSTD_DCtx_reset(ctx, ZSTD_reset_session_and_parameters); assert(!ZSTD_isError(err)); // This function doesn't actually fail (void)err;+}++size_t ZSTD_CCtx_Sizeof::operator()(const ZSTD_CCtx* ctx) const noexcept {+ return ZSTD_sizeof_CCtx(ctx);+}++size_t ZSTD_DCtx_Sizeof::operator()(const ZSTD_DCtx* ctx) const noexcept {+ return ZSTD_sizeof_DCtx(ctx);+}++void ZSTD_CCtx_Pool_Callback::operator()() const {+ if (pool_) {+ if (auto multiplier = FLAGS_folly_zstd_cctx_pool_stripes_cpu_multiplier;+ multiplier > 0.0) {+ static const size_t num_cores = folly::hardware_concurrency();+ const size_t num_stripes = std::min(+ static_cast<size_t>(std::ceil(multiplier * num_cores)),+ ZSTD_CCtx_Pool::kMaxNumStripes);+ pool_->setSize(num_stripes);+ }+ }+}++void ZSTD_DCtx_Pool_Callback::operator()() const {+ if (pool_) {+ if (auto multiplier = FLAGS_folly_zstd_dctx_pool_stripes_cpu_multiplier;+ multiplier > 0.0) {+ static const size_t num_cores = folly::hardware_concurrency();+ const size_t num_stripes = std::min(+ static_cast<size_t>(std::ceil(multiplier * num_cores)),+ ZSTD_DCtx_Pool::kMaxNumStripes);+ pool_->setSize(num_stripes);+ }+ } } ZSTD_CCtx_Pool::Ref getZSTD_CCtx() {
@@ -23,10 +23,18 @@ #endif #include <folly/compression/CompressionCoreLocalContextPool.h>+#include <folly/portability/GFlags.h> // When this header is present, folly/compression/Compression.h defines // FOLLY_COMPRESSION_HAS_CONTEXT_POOL_SINGLETONS. +// These flags allow for tuning the number of stripes in the singleton+// context pool. This is expressed as a multiplier of CPU hardware+// concurrency, e.g. a setting of 0.1 on a machine with 50 cores results+// in 5 stripes in the context pool singleton.+FOLLY_GFLAGS_DECLARE_double(folly_zstd_cctx_pool_stripes_cpu_multiplier);+FOLLY_GFLAGS_DECLARE_double(folly_zstd_dctx_pool_stripes_cpu_multiplier);+ namespace folly { namespace compression { namespace contexts {@@ -60,18 +68,52 @@ void operator()(ZSTD_DCtx* ctx) const noexcept; }; +struct ZSTD_CCtx_Sizeof {+ size_t operator()(const ZSTD_CCtx* ctx) const noexcept;+};++struct ZSTD_DCtx_Sizeof {+ size_t operator()(const ZSTD_DCtx* ctx) const noexcept;+};++class ZSTD_CCtx_Pool_Callback {+ public:+ explicit constexpr ZSTD_CCtx_Pool_Callback(+ CompressionCoreLocalContextPoolBase* pool)+ : pool_(pool) {}++ void operator()() const;++ private:+ CompressionCoreLocalContextPoolBase* pool_;+};++struct ZSTD_DCtx_Pool_Callback {+ public:+ explicit constexpr ZSTD_DCtx_Pool_Callback(+ CompressionCoreLocalContextPoolBase* pool)+ : pool_(pool) {}++ void operator()() const;++ private:+ CompressionCoreLocalContextPoolBase* pool_;+};+ using ZSTD_CCtx_Pool = CompressionCoreLocalContextPool< ZSTD_CCtx, ZSTD_CCtx_Creator, ZSTD_CCtx_Deleter, ZSTD_CCtx_Resetter,- 4>;+ ZSTD_CCtx_Sizeof,+ ZSTD_CCtx_Pool_Callback>; using ZSTD_DCtx_Pool = CompressionCoreLocalContextPool< ZSTD_DCtx, ZSTD_DCtx_Creator, ZSTD_DCtx_Deleter, ZSTD_DCtx_Resetter,- 16>;+ ZSTD_DCtx_Sizeof,+ ZSTD_DCtx_Pool_Callback>; /** * Returns a clean ZSTD_CCtx.
@@ -23,6 +23,17 @@ namespace compression { /**+ * Non-templated base class which allows for generic interaction with context+ * pool instances.+ */+class CompressionCoreLocalContextPoolBase {+ public:+ virtual ~CompressionCoreLocalContextPoolBase() = default;++ virtual void setSize(size_t size) = 0;+};++/** * This class is intended to reduce contention on reserving a compression * context and improve cache locality (but maybe not hotness) of the contexts * it manages.@@ -41,17 +52,17 @@ typename Creator, typename Deleter, typename Resetter,- size_t NumStripes = 8>-class CompressionCoreLocalContextPool {+ typename Sizeof,+ typename Callback = CompressionContextPoolDefaultCallback>+class CompressionCoreLocalContextPool+ : public CompressionCoreLocalContextPoolBase { private: /** * Force each pointer to be on a different cache line. */ class alignas(folly::hardware_destructive_interference_size) Storage { public:- Storage() : ptr(nullptr) {}-- std::atomic<T*> ptr;+ std::atomic<T*> ptr{nullptr}; }; class ReturnToPoolDeleter {@@ -61,7 +72,8 @@ Creator, Deleter, Resetter,- NumStripes>;+ Sizeof,+ Callback>; explicit ReturnToPoolDeleter(Pool* pool) : pool_(pool) { DCHECK(pool_); } @@ -71,21 +83,37 @@ Pool* pool_; }; - using BackingPool = CompressionContextPool<T, Creator, Deleter, Resetter>;+ using BackingPool =+ CompressionContextPool<T, Creator, Deleter, Resetter, Sizeof, Callback>; using BackingPoolRef = typename BackingPool::Ref; public:+ /**+ * The max size is derived from maximum stripes for folly::AccessSpreader.+ */+ static constexpr size_t kMaxNumStripes =+ folly::detail::AccessSpreaderBase::kMaxCpus;+ using Object = T; using Ref = std::unique_ptr<T, ReturnToPoolDeleter>; - explicit CompressionCoreLocalContextPool(+ constexpr explicit CompressionCoreLocalContextPool(+ size_t numStripes = 8, Creator creator = Creator(), Deleter deleter = Deleter(),- Resetter resetter = Resetter())- : pool_(std::move(creator), std::move(deleter), std::move(resetter)),+ Resetter resetter = Resetter(),+ Sizeof size_of = Sizeof(),+ Callback callback = Callback())+ : numStripes_(numStripes),+ pool_(+ std::move(creator),+ std::move(deleter),+ std::move(resetter),+ std::move(size_of),+ std::move(callback)), caches_() {} - ~CompressionCoreLocalContextPool() { flush_shallow(); }+ ~CompressionCoreLocalContextPool() override { flush_shallow(); } Ref get() { auto ptr = local().ptr.exchange(nullptr);@@ -99,6 +127,28 @@ Ref getNull() { return Ref(nullptr, get_deleter()); } + /**+ * Update the number of stripes. This will flush the pool if the stripe count+ * changes.+ */+ void setSize(size_t numStripes) override {+ if (numStripes == 0) {+ throw_exception<std::invalid_argument>(+ "CompressionCoreLocalContextPool must have at least 1 stripe");+ }+ if (numStripes > kMaxNumStripes) {+ DCHECK(false);+ numStripes = kMaxNumStripes;+ }++ auto before = numStripes_.exchange(numStripes);+ if (before != numStripes) {+ flush_shallow();+ }+ }++ size_t cacheSize() const { return numStripes_.load(); }+ size_t created_count() const { return pool_.created_count(); } void flush_deep() {@@ -114,6 +164,16 @@ } } + size_t bytes_used() const {+ size_t bytes = pool_.bytes_used();+ for (const auto& cache : caches_) {+ if (auto ptr = cache.ptr.load()) {+ bytes += pool_.get_sizeof()(ptr);+ }+ }+ return bytes;+ }+ private: ReturnToPoolDeleter get_deleter() { return ReturnToPoolDeleter(this); } @@ -131,12 +191,21 @@ } Storage& local() {- const auto idx = folly::AccessSpreader<>::cachedCurrent(NumStripes);+ // Note that cachedCurrent(0) is valid, so this should be SIOF safe.+ const auto idx = folly::AccessSpreader<>::cachedCurrent(numStripes_); return caches_[idx]; } + relaxed_atomic<size_t> numStripes_; BackingPool pool_;- std::array<Storage, NumStripes> caches_{};++ /**+ * context_pool_max_num_stripes number of stripes are allocated to the+ * underlying stripes array. However, only the lower numStripes_ indices are+ * used to actually stripe the pool. This allows us to statically create+ * singletons while providing flexibility around how many stripes there are.+ */+ std::array<Storage, kMaxNumStripes> caches_; }; } // namespace compression } // namespace folly
@@ -251,8 +251,8 @@ template <class Instructions> auto QuotientMultiSet<Instructions>::findRunend(- uint64_t occupiedRank,- uint64_t firstRunend) const -> std::pair<uint64_t, const Block*> {+ uint64_t occupiedRank, uint64_t firstRunend) const+ -> std::pair<uint64_t, const Block*> { // Look for the right runend. size_t slotBlockIndex = firstRunend / kBlockSize; auto block = getBlock(slotBlockIndex);
@@ -226,16 +226,20 @@ // is also not supported for gzip and raw deflation. // Hence, the codec supports only 9..15. if (!inBounds(options_.windowSize, 9, 15)) {- throw std::invalid_argument(to<std::string>(- "ZlibStreamCodec: invalid windowSize option: ", options.windowSize));+ throw std::invalid_argument(+ to<std::string>(+ "ZlibStreamCodec: invalid windowSize option: ",+ options.windowSize)); } if (!inBounds(options_.memLevel, 1, 9)) {- throw std::invalid_argument(to<std::string>(- "ZlibStreamCodec: invalid memLevel option: ", options.memLevel));+ throw std::invalid_argument(+ to<std::string>(+ "ZlibStreamCodec: invalid memLevel option: ", options.memLevel)); } if (!isValidStrategy(options_.strategy)) {- throw std::invalid_argument(to<std::string>(- "ZlibStreamCodec: invalid strategy: ", options.strategy));+ throw std::invalid_argument(+ to<std::string>(+ "ZlibStreamCodec: invalid strategy: ", options.strategy)); } }
@@ -21,8 +21,6 @@ #include <stdexcept> #include <string> -#include <zstd.h>- #include <folly/Conv.h> #include <folly/Range.h> #include <folly/ScopeGuard.h>
@@ -65,8 +65,8 @@ Pointer forwardPointers = nullptr; }; -typedef BitVectorCompressedListBase<const uint8_t*> BitVectorCompressedList;-typedef BitVectorCompressedListBase<uint8_t*> MutableBitVectorCompressedList;+using BitVectorCompressedList = BitVectorCompressedListBase<const uint8_t*>;+using MutableBitVectorCompressedList = BitVectorCompressedListBase<uint8_t*>; template < class Value,@@ -78,11 +78,11 @@ std::is_integral<Value>::value && std::is_unsigned<Value>::value, "Value should be unsigned integral"); - typedef BitVectorCompressedList CompressedList;- typedef MutableBitVectorCompressedList MutableCompressedList;+ using CompressedList = BitVectorCompressedList;+ using MutableCompressedList = MutableBitVectorCompressedList; - typedef Value ValueType;- typedef SkipValue SkipValueType;+ using ValueType = Value;+ using SkipValueType = SkipValue; struct Layout; static constexpr size_t skipQuantum = kSkipQuantum;@@ -242,11 +242,11 @@ : detail::ForwardPointers<Encoder::forwardQuantum>, detail::SkipPointers<Encoder::skipQuantum> { public:- typedef Encoder EncoderType;- typedef typename Encoder::ValueType ValueType;+ using EncoderType = Encoder;+ using ValueType = typename Encoder::ValueType; // A bitvector can only be as large as its largest value.- typedef typename Encoder::ValueType SizeType;- typedef typename Encoder::SkipValueType SkipValueType;+ using SizeType = typename Encoder::ValueType;+ using SkipValueType = typename Encoder::SkipValueType; explicit BitVectorReader(const typename Encoder::CompressedList& list) : detail::ForwardPointers<Encoder::forwardQuantum>(list.forwardPointers),
@@ -879,7 +879,7 @@ return readLowerPart(position()) | (upper_.value() << numLowerBits_); } - // Ordering of fields is counter-intutive but it optimizes the layout.+ // Ordering of fields is counter-intuitive but it optimizes the layout. UpperBitsReader upper_; const uint8_t* const lower_; ValueType value_;
@@ -33,6 +33,8 @@ #define FOLLY_HAS_ATOMIC_SHARED_PTR_HOOKED 0 #endif +namespace folly {+ #if FOLLY_HAS_ATOMIC_SHARED_PTR_HOOKED /*@@ -77,7 +79,6 @@ // A note on noexcept: If the pointer is an aliased pointer, // store() will allocate. Otherwise is noexcept.-namespace folly { template < typename T,@@ -393,12 +394,8 @@ mutable AtomicStruct<PackedPtr, Atom> ptr_; }; -} // namespace folly- #else -namespace folly {- template <typename T> class atomic_shared_ptr { private:@@ -479,6 +476,9 @@ } }; -} // namespace folly- #endif // FOLLY_HAS_ATOMIC_SHARED_PTR_HOOKED++template <typename T>+atomic_shared_ptr(std::shared_ptr<T>) -> atomic_shared_ptr<T>;++} // namespace folly
@@ -20,6 +20,9 @@ #define _GNU_SOURCE 1 // for RTLD_NOLOAD #include <dlfcn.h> #endif+#include <string.h>++#include <filesystem> #include <fstream> #include <mutex> #include <numeric>@@ -31,9 +34,11 @@ #include <folly/Indestructible.h> #include <folly/Memory.h> #include <folly/ScopeGuard.h>-#include <folly/detail/StaticSingletonManager.h>+#include <folly/container/Reserve.h> #include <folly/hash/Hash.h> #include <folly/lang/Exception.h>+#include <folly/portability/Fcntl.h>+#include <folly/portability/FmtCompile.h> #include <folly/portability/Unistd.h> #include <folly/system/ThreadId.h> @@ -42,16 +47,19 @@ ///////////// CacheLocality /// Returns the CacheLocality information best for this machine-static CacheLocality getSystemLocalityInfo() {+CacheLocality CacheLocality::readSystemLocalityInfo() { if (kIsLinux) {- // First try to parse /proc/cpuinfo.- // If that fails, then try to parse /sys/devices/.- // The latter is slower but more accurate.- try {- return CacheLocality::readFromProcCpuinfo();- } catch (...) {- // /proc/cpuinfo might be non-standard- // lets try with sysfs /sys/devices/cpu+ if (kIsArchAmd64 || kIsArchX86) {+ // First try to parse /proc/cpuinfo.+ // But only on arch's where the file has cpu topology hints.+ // If that fails, then try to parse /sys/devices/.+ // The latter is slower but more accurate.+ try {+ return CacheLocality::readFromProcCpuinfo();+ } catch (...) {+ // /proc/cpuinfo might be non-standard+ // lets try with sysfs /sys/devices/cpu+ } } try {@@ -83,7 +91,7 @@ if (value != nullptr) { return *value; }- auto next = new CacheLocality(getSystemLocalityInfo());+ auto next = new CacheLocality(readSystemLocalityInfo()); if (cache.compare_exchange_strong(value, next, std::memory_order_acq_rel)) { return *next; }@@ -172,20 +180,77 @@ return val; } -CacheLocality CacheLocality::readFromSysfsTree(- const std::function<std::string(std::string const&)>& mapping) {+CacheLocality CacheLocality::readFromSysfsTree(std::string_view root) {+#if defined(_WIN32)+ // windows does not have openat and open flag constants+ return CacheLocality::uniform(0);+#else+ // the list of cache equivalence classes, where equivalence classes // are named by the smallest cpu in the class std::vector<std::vector<size_t>> equivClassesByCpu; + auto checkNoEnt = [](std::string_view name) {+ auto err = errno;+ if (err != ENOENT) {+ throw std::runtime_error(+ fmt::format(+ "unexpected error while opening {}: {}", name, strerror(err)));+ }+ };++ // Reads the first 64 bytes of the file.+ auto rdfile64 = [&](int dirfd, const std::string& name) {+ const auto fd = ::openat(dirfd, name.c_str(), O_CLOEXEC, O_RDONLY);+ if (fd < 0) {+ checkNoEnt(name);+ return std::string(); // stop condition for the inner loop below+ }+ SCOPE_EXIT {+ ::close(fd);+ };++ alignas(64) char buf[64];+ int ret = 0;+ do {+ ret = ::pread(fd, buf, sizeof(buf), 0);+ } while (ret < 0 && errno == EINTR);+ if (ret < 0) {+ return std::string();+ }+ return std::string(buf, to_unsigned(ret));+ };++ auto subroot = std::filesystem::path(root) / "sys/devices/system/cpu";+ const int allfd = ::open(subroot.c_str(), O_DIRECTORY | O_CLOEXEC, O_RDONLY);+ if (allfd < 0) {+ auto err = errno;+ throw std::runtime_error(+ fmt::format("unable to open sysfs: {}", strerror(err)));+ }+ SCOPE_EXIT {+ ::close(allfd);+ };++ size_t maxindex = 0; for (size_t cpu = 0;; ++cpu) {+ auto cpuroot = fmt::format(FMT_COMPILE("cpu{}/cache"), cpu);+ const int cpufd =+ ::openat(allfd, cpuroot.c_str(), O_DIRECTORY | O_CLOEXEC, O_RDONLY);+ if (cpufd < 0) {+ checkNoEnt(cpuroot);+ break;+ }+ SCOPE_EXIT {+ ::close(cpufd);+ };+ std::vector<size_t> levels;+ grow_capacity_by(levels, maxindex); for (size_t index = 0;; ++index) {- auto dir = fmt::format(- "/sys/devices/system/cpu/cpu{}/cache/index{}/", cpu, index);- auto cacheType = mapping(dir + "type");- auto equivStr = mapping(dir + "shared_cpu_list");- if (cacheType.empty() || equivStr.empty()) {+ auto dir = fmt::format(FMT_COMPILE("index{}/"), index);+ auto cacheType = rdfile64(cpufd, dir + "type");+ if (cacheType.empty()) { // no more caches break; }@@ -193,9 +258,16 @@ // cacheType in { "Data", "Instruction", "Unified" }. skip icache continue; }+ // only try to read the second file once we know we will need it+ auto equivStr = rdfile64(cpufd, dir + "shared_cpu_list");+ if (equivStr.empty()) {+ // no more caches+ break;+ } auto equiv = parseLeadingNumber(equivStr); levels.push_back(equiv); }+ maxindex = std::max(maxindex, levels.size()); if (levels.empty()) { // no levels at all for this cpu, we must be done@@ -209,15 +281,12 @@ } return CacheLocality{std::move(equivClassesByCpu)};++#endif } CacheLocality CacheLocality::readFromSysfs() {- return readFromSysfsTree([](std::string const& name) {- std::ifstream xi(name.c_str());- std::string rv;- std::getline(xi, rv);- return rv;- });+ return readFromSysfsTree(); } namespace {@@ -520,6 +589,11 @@ folly::aligned_free(block); } }++ SimpleAllocator(const SimpleAllocator&) = delete;+ SimpleAllocator& operator=(const SimpleAllocator&) = delete;+ SimpleAllocator(SimpleAllocator&&) = delete;+ SimpleAllocator& operator=(SimpleAllocator&&) = delete; void* allocate() { std::lock_guard g(m_);
@@ -23,6 +23,7 @@ #include <functional> #include <limits> #include <string>+#include <string_view> #include <type_traits> #include <vector> @@ -35,10 +36,10 @@ // This file contains several classes that might be useful if you are // trying to dynamically optimize cache locality: CacheLocality reads-// cache sharing information from sysfs to determine how CPUs should be-// grouped to minimize contention, Getcpu provides fast access to the-// current CPU via __vdso_getcpu, and AccessSpreader uses these two to-// optimally spread accesses among a predetermined number of stripes.+// cache sharing information from procfs or sysfs to determine how CPUs+// should be grouped to minimize contention, Getcpu provides fast access+// to the current CPU via __vdso_getcpu, and AccessSpreader uses these two+// to optimally spread accesses among a predetermined number of stripes. // // AccessSpreader<>::current(n) microbenchmarks at 22 nanos, which is // substantially less than the cost of a cache miss. This means that we@@ -82,9 +83,9 @@ std::vector<std::vector<size_t>> equivClassesByCpu; /// Returns the best CacheLocality information available for the current- /// system, cached for fast access. This will be loaded from sysfs if- /// possible, otherwise it will be correct in the number of CPUs but- /// not in their sharing structure.+ /// system, cached for fast access. This will be loaded from procfs or+ /// sysfs if possible, otherwise it will be correct in the number of CPUs+ /// but not in their sharing structure. /// /// If you are into yo dawgs, this is a shared cache of the local /// locality of the shared caches.@@ -93,11 +94,17 @@ /// repeatable CacheLocality structure during testing. Rather than /// inject the type of the CacheLocality provider into every data type /// that transitively uses it, all components select between the default- /// sysfs implementation and a deterministic implementation by keying+ /// procfs/sysfs implementation and a deterministic implementation by keying /// off the type of the underlying atomic. See DeterministicScheduler. template <template <typename> class Atom = std::atomic> static const CacheLocality& system(); + /// Returns the best CacheLocality information available for the current+ /// system. This will be loaded from procfs or sysfs if possible, otherwise+ /// it will be correct in the number of CPUs but not in their sharing+ /// structure.+ static CacheLocality readSystemLocalityInfo();+ /// Reads CacheLocality information from a tree structured like /// the sysfs filesystem. The provided function will be evaluated /// for each sysfs file that needs to be queried. The function@@ -106,8 +113,7 @@ /// not exist. The function will be called with paths of the form /// /sys/devices/system/cpu/cpu*/cache/index*/{type,shared_cpu_list} . /// Throws an exception if no caches can be parsed at all.- static CacheLocality readFromSysfsTree(- const std::function<std::string(std::string const&)>& mapping);+ static CacheLocality readFromSysfsTree(std::string_view root = "/"); /// Reads CacheLocality information from the real sysfs filesystem. /// Throws an exception if no cache information can be loaded.@@ -180,7 +186,7 @@ namespace detail { class AccessSpreaderBase {- protected:+ public: /// If there are more cpus than this nothing will crash, but there /// might be unnecessary sharing enum {@@ -188,6 +194,7 @@ kMaxCpus = kIsMobile ? 16 : 256, }; + protected: using CompactStripe = uint8_t; static_assert(@@ -467,10 +474,15 @@ }; }; -class FOLLY_NODISCARD CoreAllocatorGuard {+class [[nodiscard]] CoreAllocatorGuard { public: CoreAllocatorGuard(size_t numStripes, size_t stripe); ~CoreAllocatorGuard();++ CoreAllocatorGuard(const CoreAllocatorGuard&) = delete;+ CoreAllocatorGuard& operator=(const CoreAllocatorGuard&) = delete;+ CoreAllocatorGuard(CoreAllocatorGuard&&) = delete;+ CoreAllocatorGuard& operator=(CoreAllocatorGuard&&) = delete; private: friend void* detail::coreMallocFromGuard(size_t size);
@@ -137,10 +137,8 @@ typename, typename, typename,- template <typename>- class,- class>- class Impl = detail::concurrenthashmap::bucket::BucketTable>+ template <typename> class,+ class> class Impl = detail::concurrenthashmap::bucket::BucketTable> class ConcurrentHashMap { using SegmentT = detail::ConcurrentHashMapSegment< KeyType,@@ -166,13 +164,13 @@ public: class ConstIterator; - typedef KeyType key_type;- typedef ValueType mapped_type;- typedef std::pair<const KeyType, ValueType> value_type;- typedef std::size_t size_type;- typedef HashFn hasher;- typedef KeyEqual key_equal;- typedef ConstIterator const_iterator;+ using key_type = KeyType;+ using mapped_type = ValueType;+ using value_type = std::pair<const KeyType, ValueType>;+ using size_type = std::size_t;+ using hasher = HashFn;+ using key_equal = KeyEqual;+ using const_iterator = ConstIterator; private: template <typename K, typename T>@@ -295,6 +293,16 @@ return findImpl(k); } + /*+ * Intentionally marked as deleted to guard against common misuse.+ *+ * Checking for key existence via contains() is unsafe in concurrent+ * contexts. The key may be erased between contains() and subsequent+ * operations. Use find() and its iterator to ensure safe access and+ * prevent race conditions.+ */+ bool contains(const KeyType& k) const = delete;+ ConstIterator cend() const noexcept { return ConstIterator(NumShards); } ConstIterator cbegin() const noexcept { return ConstIterator(this); }@@ -513,16 +521,12 @@ // Erase if and only if key k is equal to expected size_type erase_if_equal(const key_type& k, const ValueType& expected) {- return erase_key_if(k, [&expected](const ValueType& v) {- return v == expected;- });+ return erase_key_if(k, detail::concurrenthashmap::EqualTo1{expected}); } template <typename K, EnableHeterogeneousErase<K, int> = 0> size_type erase_if_equal(const K& k, const ValueType& expected) {- return erase_key_if(k, [&expected](const ValueType& v) {- return v == expected;- });+ return erase_key_if(k, detail::concurrenthashmap::EqualTo1{expected}); } // Erase if predicate evaluates to true on the existing value@@ -553,8 +557,9 @@ void reserve(size_t count) { count = count >> ShardBits;- if (!count)+ if (!count) { return;+ } uint64_t begin = beginSeg_.load(std::memory_order_acquire); uint64_t end = endSeg_.load(std::memory_order_acquire); for (uint64_t i = begin; i < end; ++i) {@@ -837,7 +842,9 @@ ShardBits, Atom, Mutex,-#if (FOLLY_SSE_PREREQ(4, 2) || FOLLY_AARCH64) && \+#if ( \+ FOLLY_SSE_PREREQ(4, 2) || \+ (FOLLY_AARCH64 && FOLLY_F14_CRC_INTRINSIC_AVAILABLE)) && \ FOLLY_F14_VECTOR_INTRINSICS_AVAILABLE detail::concurrenthashmap::simd::SIMDTable #else
@@ -80,9 +80,10 @@ using GetLifetime = LocalLifetime&(); struct Arg {- GetGlobal& global;- GetLocal& local;- GetLifetime& lifetime;+ // To be constexpr, MSVC requires these to be pointers.+ GetGlobal* global;+ GetLocal* local;+ GetLifetime* lifetime; }; // LocalLifetime@@ -245,7 +246,7 @@ // ensure this invariant. FOLLY_ERASE_NOINLINE static void mutate_slow(Signed v) noexcept {- static constexpr Arg arg{global, local, lifetime};+ static constexpr Arg arg{&global, &local, &lifetime}; mutate_slow(v, arg); }
@@ -121,7 +121,7 @@ : truth_{static_cast<A&&>(a)...} {} template <typename A, if_<std::is_assignable<value_type&, A>{}> = 0>- thread_cached_synchronized& operator=(A&& a) noexcept(false) {+ thread_cached_synchronized& operator=(A && a) noexcept(false) { store(static_cast<A&&>(a)); return *this; }
@@ -284,8 +284,7 @@ typename T, typename PriorityQueue, typename Mutex,- template <typename>- class Atom>+ template <typename> class Atom> template <typename Clock, typename Duration> inline bool FlatCombiningPriorityQueue<T, PriorityQueue, Mutex, Atom>::try_push_impl(@@ -341,8 +340,7 @@ typename T, typename PriorityQueue, typename Mutex,- template <typename>- class Atom>+ template <typename> class Atom> template <typename Clock, typename Duration> inline bool FlatCombiningPriorityQueue<T, PriorityQueue, Mutex, Atom>::try_pop_impl(@@ -387,8 +385,7 @@ typename T, typename PriorityQueue, typename Mutex,- template <typename>- class Atom>+ template <typename> class Atom> template <typename Clock, typename Duration> inline bool FlatCombiningPriorityQueue<T, PriorityQueue, Mutex, Atom>::try_peek_impl(
@@ -34,10 +34,8 @@ template < typename T,- template <typename>- class Atom,- template <typename>- class Storage>+ template <typename> class Atom,+ template <typename> class Storage> class RingBufferSlot; template <typename T> class RingBufferTrivialStorage;@@ -201,10 +199,8 @@ namespace detail { template < typename T,- template <typename>- class Atom,- template <typename>- class Storage>+ template <typename> class Atom,+ template <typename> class Storage> class RingBufferSlot { public: explicit RingBufferSlot() noexcept {}
@@ -867,8 +867,10 @@ int num = std::min(rsize, (uint32_t)PopBatch); for (int i = num - 1; i >= 0; i--) { // wait until this block is empty- while (shared_buffer_[i].pnode.load(std::memory_order_relaxed) != nullptr)+ while (+ shared_buffer_[i].pnode.load(std::memory_order_relaxed) != nullptr) { ;+ } shared_buffer_[i].pnode.store(head, std::memory_order_relaxed); head = head->next; }@@ -1078,7 +1080,7 @@ const size_t& curticket, const std::chrono::time_point<Clock, Duration>& deadline, const folly::WaitOptions& opt = wait_options()) {- return folly::detail::spin_pause_until(deadline, opt, [=] {+ return folly::detail::spin_pause_until(deadline, opt, [=, this] { return futexIsReady(curticket); }) == folly::detail::spin_result::success; }@@ -1143,7 +1145,7 @@ const std::chrono::time_point<Clock, Duration>& deadline, const folly::WaitOptions& opt = wait_options()) { // Fast path, by quick check the status- switch (folly::detail::spin_pause_until(deadline, opt, [=] {+ switch (folly::detail::spin_pause_until(deadline, opt, [=, this] { return !isEmpty(); })) { case folly::detail::spin_result::success:@@ -1156,7 +1158,7 @@ // Spinning strategy while (true) {- auto res = folly::detail::spin_yield_until(deadline, [=] {+ auto res = folly::detail::spin_yield_until(deadline, [=, this] { return !isEmpty(); }); if (res == folly::detail::spin_result::success) {
@@ -35,8 +35,11 @@ /// atomic_grow_array_policy_default /// /// A default or example policy for use with atomic_grow_array.-template <typename Item>+template <typename Item, template <typename> class Atom = std::atomic> struct atomic_grow_array_policy_default {+ template <typename V>+ using atom = Atom<V>;+ std::size_t grow( std::size_t /* const curr */, std::size_t const index) const noexcept { return nextPowTwo(index + 1);@@ -73,6 +76,33 @@ /// capacity * sizeof(value_type) <--- array-segment elements slab /// Modulo allocator size-classes, of which this container makes no attempt to /// take advantage, and value-type alignment.+///+/// This is a concurrent array. These are the available operations. They are all+/// safely concurrent with each other and they all provide the required memory+/// orderings for safe concurrent use:+/// operator[]+/// size+/// empty+/// as_view+/// as_ptr_span+///+/// The container has reference and iterator stability. The references, views,+/// and pointer-spans returned by operator[], as_view, and as_ptr_span are valid+/// for the lifetime of the container and will never become dangling.+///+/// The results of empty and size are monotonic: callers in happens-before order+/// will only ever see nondecreasing values returned by empty or by size. These+/// values serve as lower bounds valid for the lifetime of the container.+///+/// Example:+///+/// relaxed_atomic<size_t> count{0};+/// atomic_grow_array<object> array;+///+/// object& next() { return array[count++]; }+///+/// template <typename func>+/// void for_each(func f) { for (auto p : array.as_ptr_span(count)) f(*p); } template < typename Item, typename Policy = atomic_grow_array_policy_default<Item>>@@ -242,6 +272,15 @@ using type = span<const_pointer const>; return array_ ? type{array_->list, array_->size} : type{}; }++ span<pointer const> as_ptr_span(size_type const sz) noexcept {+ auto ptrs = as_ptr_span();+ return ptrs.subspan(0, std::min(ptrs.size(), sz));+ }+ span<const_pointer const> as_ptr_span(size_type const sz) const noexcept {+ auto ptrs = as_ptr_span();+ return ptrs.subspan(0, std::min(ptrs.size(), sz));+ } }; public:@@ -449,6 +488,12 @@ const_pointer_span as_ptr_span() const noexcept { return as_view().as_ptr_span(); }+ pointer_span as_ptr_span(size_type const sz) noexcept {+ return as_view().as_ptr_span(sz);+ }+ const_pointer_span as_ptr_span(size_type const sz) const noexcept {+ return as_view().as_ptr_span(sz);+ } private: static constexpr auto mo_acquire = std::memory_order_acquire;@@ -522,7 +567,8 @@ assert(size > base); array* curr = static_cast<array*>( operator_new(array_size(size, base), std::align_val_t{array_align()}));- auto rollback = folly::makeGuard([&] { del_array(curr); });+ auto rollback =+ folly::makeGuard(std::bind(&atomic_grow_array::del_array, this, curr)); curr->size = size; curr->next = next; auto const slab = array_slab(curr);@@ -582,8 +628,8 @@ } } - std::atomic<size_type> size_{0};- std::atomic<array*> array_{nullptr};+ typename Policy::template atom<size_type> size_{0};+ typename Policy::template atom<array*> array_{nullptr}; }; } // namespace folly
@@ -41,8 +41,8 @@ static std::shared_ptr<T> make_ptr(Args&&... args) { return std::make_shared<T>(std::forward<Args...>(args...)); }- typedef std::__shared_count<std::_S_atomic> shared_count;- typedef std::_Sp_counted_base<std::_S_atomic> counted_base;+ using shared_count = std::__shared_count<std::_S_atomic>;+ using counted_base = std::_Sp_counted_base<std::_S_atomic>; template <typename T> using CountedPtr = std::shared_ptr<T>; @@ -67,38 +67,38 @@ private: /* Accessors for private members using explicit template instantiation */ struct access_shared_ptr {- typedef shared_count std::__shared_ptr<const void, std::_S_atomic>::*type;+ using type = shared_count std::__shared_ptr<const void, std::_S_atomic>::*; friend type fieldPtr(access_shared_ptr); }; struct access_base {- typedef counted_base* shared_count::*type;+ using type = counted_base* shared_count::*; friend type fieldPtr(access_base); }; struct access_use_count {- typedef _Atomic_word counted_base::*type;+ using type = _Atomic_word counted_base::*; friend type fieldPtr(access_use_count); }; struct access_weak_count {- typedef _Atomic_word counted_base::*type;+ using type = _Atomic_word counted_base::*; friend type fieldPtr(access_weak_count); }; struct access_counted_ptr_ptr {- typedef const void* std::_Sp_counted_ptr<const void*, std::_S_atomic>::*- type;+ using type =+ const void* std::_Sp_counted_ptr<const void*, std::_S_atomic>::*; friend type fieldPtr(access_counted_ptr_ptr); }; struct access_shared_ptr_ptr {- typedef const void* std::__shared_ptr<const void, std::_S_atomic>::*type;+ using type = const void* std::__shared_ptr<const void, std::_S_atomic>::*; friend type fieldPtr(access_shared_ptr_ptr); }; struct access_refcount {- typedef shared_count std::__shared_ptr<const void, std::_S_atomic>::*type;+ using type = shared_count std::__shared_ptr<const void, std::_S_atomic>::*; friend type fieldPtr(access_refcount); };
@@ -18,16 +18,18 @@ #include <algorithm> #include <atomic>+#include <memory> #include <mutex> #include <new> #include <folly/ScopeGuard.h> #include <folly/container/HeterogeneousAccess.h> #include <folly/container/detail/F14Mask.h>+#include <folly/lang/Align.h> #include <folly/lang/Exception.h> #include <folly/synchronization/Hazptr.h> -#ifdef __aarch64__+#if FOLLY_AARCH64 && FOLLY_F14_CRC_INTRINSIC_AVAILABLE #include <arm_acle.h> #include <arm_neon.h> #elif FOLLY_SSE_PREREQ(4, 2) && !FOLLY_MOBILE@@ -40,6 +42,44 @@ namespace concurrenthashmap { +template <typename Class, typename ValueType, typename Allocator>+class alignas(alignof(ValueType)) AlignedStorage {+ struct alignas(alignof(Class)) Dummy {};++ using DummyAllocator =+ typename std::allocator_traits<Allocator>::template rebind_alloc<Dummy>;+ using DummyAllocTraits = std::allocator_traits<DummyAllocator>;++ static size_t calculateNeededDummyCount(size_t count) {+ const size_t sz = sizeof(Class) + sizeof(ValueType) * count;+ static_assert(alignof(Dummy) % alignof(ValueType) == 0);+ static_assert(alignof(Dummy) % alignof(Class) == 0);+ const size_t aligned_sz = align_ceil(sz, alignof(Dummy));+ return aligned_sz / sizeof(Dummy);+ }++ public:+ ValueType* array() noexcept {+ return reinterpret_cast<ValueType*>(static_cast<Class*>(this) + 1);+ }++ protected:+ static Class* allocate(size_t count) {+ auto allocator = DummyAllocator();+ auto* buf =+ DummyAllocTraits::allocate(allocator, calculateNeededDummyCount(count));+ return reinterpret_cast<Class*>(buf);+ }++ void deallocate(size_t count) {+ auto allocator = DummyAllocator();+ DummyAllocTraits::deallocate(+ allocator,+ reinterpret_cast<Dummy*>(static_cast<Class*>(this)),+ calculateNeededDummyCount(count));+ }+};+ enum class InsertType { DOES_NOT_EXIST, // insert/emplace operations. If key exists, return false. MUST_EXIST, // assign operations. If key does not exist, return false.@@ -55,12 +95,11 @@ typename KeyType, typename ValueType, typename Allocator,- template <typename>- class Atom,+ template <typename> class Atom, typename Enabled = void> class ValueHolder { public:- typedef std::pair<const KeyType, ValueType> value_type;+ using value_type = std::pair<const KeyType, ValueType>; explicit ValueHolder(const ValueHolder& other) : item_(other.item_) {} @@ -83,8 +122,7 @@ typename KeyType, typename ValueType, typename Allocator,- template <typename>- class Atom>+ template <typename> class Atom> class ValueHolder< KeyType, ValueType,@@ -93,7 +131,7 @@ std::enable_if_t< !std::is_nothrow_copy_constructible<ValueType>::value || !std::is_nothrow_copy_constructible<KeyType>::value>> {- typedef std::pair<const KeyType, ValueType> value_type;+ using value_type = std::pair<const KeyType, ValueType>; struct CountedItem { value_type kv_;@@ -123,6 +161,13 @@ }; // CountedItem // Back to ValueHolder specialization + struct DeallocateFn { // Avoids dependent lambdas in function templates+ ValueHolder& self;+ void operator()() const noexcept {+ Allocator().deallocate((uint8_t*)self.item_, sizeof(CountedItem));+ }+ };+ CountedItem* item_; // Link to unique key-value item. public:@@ -137,9 +182,7 @@ template <typename Arg, typename... Args> ValueHolder(std::piecewise_construct_t, Arg&& k, Args&&... args) { item_ = (CountedItem*)Allocator().allocate(sizeof(CountedItem));- auto g = makeGuard([&] {- Allocator().deallocate((uint8_t*)item_, sizeof(CountedItem));- });+ auto g = makeGuard(DeallocateFn{*this}); new (item_) CountedItem( std::piecewise_construct, std::forward<Arg>(k),@@ -163,6 +206,13 @@ template <typename Node, typename Allocator> struct AllocNodeGuard : NonCopyableNonMovable {+ struct DeallocateFn { // Avoids dependent lambdas in function templates+ AllocNodeGuard& self;+ void operator()() const noexcept {+ self.alloc.deallocate((uint8_t*)self.node, sizeof(Node));+ }+ };+ Allocator alloc; Node* node{}; @@ -171,10 +221,8 @@ template <typename... Arg> explicit AllocNodeGuard(Allocator alloc_, Arg&&... arg)- : alloc{std::move(alloc_)}, node{(Node*)alloc_.allocate(sizeof(Node))} {- auto guard = makeGuard([&] {- alloc.deallocate((uint8_t*)node, sizeof(Node));- });+ : alloc{std::move(alloc_)}, node{(Node*)alloc.allocate(sizeof(Node))} {+ auto guard = makeGuard(DeallocateFn{*this}); new (node) Node(std::forward<Arg>(arg)...); guard.dismiss(); }@@ -228,7 +276,7 @@ Atom, concurrenthashmap::HazptrDeleter<Allocator>> { public:- typedef std::pair<const KeyType, ValueType> value_type;+ using value_type = std::pair<const KeyType, ValueType>; explicit NodeT(hazptr_obj_cohort<Atom>* cohort, NodeT* other) : item_(other->item_) {@@ -287,7 +335,7 @@ // perfectly balanced, reserve(size) will still work without // rehashing. static constexpr float kDefaultLoadFactor = 1.05f;- typedef std::pair<const KeyType, ValueType> value_type;+ using value_type = std::pair<const KeyType, ValueType>; using Node = concurrenthashmap::bucket::NodeT<KeyType, ValueType, Allocator, Atom>;@@ -380,7 +428,7 @@ load_factor_nodes_ = to_integral(static_cast<float>(bucket_count) * load_factor_); for (size_t i = 0; i < oldcount; i++) {- auto bucket = &buckets->buckets_[i]();+ auto bucket = &buckets->array()[i](); auto node = bucket->load(std::memory_order_relaxed); if (!node) { continue;@@ -404,14 +452,14 @@ } // Set longest last run in new bucket, incrementing the refcount. lastrun->acquire_link(); // defined in hazptr_obj_base_linked- newbuckets->buckets_[lastidx]().store(lastrun, std::memory_order_relaxed);+ newbuckets->array()[lastidx]().store(lastrun, std::memory_order_relaxed); // Clone remaining nodes for (; node != lastrun; node = node->next_.load(std::memory_order_relaxed)) { auto newnode = (Node*)Allocator().allocate(sizeof(Node)); new (newnode) Node(cohort, node); auto k = getIdx(bucket_count, HashFn()(node->getItem().first));- auto prevhead = &newbuckets->buckets_[k]();+ auto prevhead = &newbuckets->array()[k](); newnode->next_.store(prevhead->load(std::memory_order_relaxed)); prevhead->store(newnode, std::memory_order_relaxed); }@@ -435,7 +483,7 @@ getBucketsAndCount(bcount, buckets, res.hazptrs_[0]); auto idx = getIdx(bcount, h);- auto prev = &buckets->buckets_[idx]();+ auto prev = &buckets->array()[idx](); auto node = hazcurr.protect(*prev); while (node) { if (KeyEqual()(k, node->getItem().first)) {@@ -458,7 +506,7 @@ auto buckets = buckets_.load(std::memory_order_relaxed); DCHECK(buckets); // Use-after-destruction by user. auto idx = getIdx(bcount, h);- auto head = &buckets->buckets_[idx]();+ auto head = &buckets->array()[idx](); node = head->load(std::memory_order_relaxed); Node* prev = nullptr; while (node) {@@ -537,45 +585,41 @@ Iterator cend() { return Iterator(nullptr); } private:- // Could be optimized to avoid an extra pointer dereference by- // allocating buckets_ at the same time.+ using BucketRoot = hazptr_root<Node, Atom>; class Buckets : public hazptr_obj_base< Buckets, Atom,- concurrenthashmap::HazptrTableDeleter> {- using BucketRoot = hazptr_root<Node, Atom>;-- Buckets() {}- ~Buckets() {}+ concurrenthashmap::HazptrTableDeleter>,+ public AlignedStorage<Buckets, BucketRoot, Allocator> {+ Buckets() = default;+ ~Buckets() = default; public: static Buckets* create(size_t count, hazptr_obj_cohort<Atom>* cohort) {- auto buf =- Allocator().allocate(sizeof(Buckets) + sizeof(BucketRoot) * count);- auto buckets = new (buf) Buckets();+ auto* buf = Buckets::allocate(count);+ auto* buckets = new (buf) Buckets(); DCHECK(cohort); buckets->set_cohort_tag(cohort); // defined in hazptr_obj for (size_t i = 0; i < count; i++) {- new (&buckets->buckets_[i]) BucketRoot;+ new (&buckets->array()[i]) BucketRoot; } return buckets; } void destroy(size_t count) { for (size_t i = 0; i < count; i++) {- buckets_[i].~BucketRoot();+ Buckets::array()[i].~BucketRoot(); } this->~Buckets();- Allocator().deallocate(- (uint8_t*)this, sizeof(BucketRoot) * count + sizeof(*this));+ Buckets::deallocate(count); } void unlink_and_reclaim_nodes(size_t count) { for (size_t i = 0; i < count; i++) {- auto node = buckets_[i]().load(std::memory_order_relaxed);+ auto node = Buckets::array()[i]().load(std::memory_order_relaxed); if (node) {- buckets_[i]().store(nullptr, std::memory_order_relaxed);+ Buckets::array()[i]().store(nullptr, std::memory_order_relaxed); while (node) { auto next = node->next_.load(std::memory_order_relaxed); if (next) {@@ -587,8 +631,6 @@ } } }-- BucketRoot buckets_[0]; }; public:@@ -634,7 +676,7 @@ break; } DCHECK(buckets_);- node_ = hazptrs_[1].protect(buckets_->buckets_[idx_]());+ node_ = hazptrs_[1].protect(buckets_->array()[idx_]()); if (node_) { break; }@@ -727,7 +769,7 @@ DCHECK(buckets) << "Use-after-destruction by user."; auto idx = getIdx(bcount, h);- auto head = &buckets->buckets_[idx]();+ auto head = &buckets->array()[idx](); auto node = head->load(std::memory_order_relaxed); auto headnode = node; auto prev = head;@@ -794,7 +836,7 @@ bcount <<= 1; hazbuckets.reset_protection(buckets); idx = getIdx(bcount, h);- head = &buckets->buckets_[idx]();+ head = &buckets->array()[idx](); headnode = head->load(std::memory_order_relaxed); } @@ -830,7 +872,9 @@ } // namespace bucket -#if (FOLLY_SSE_PREREQ(4, 2) || FOLLY_AARCH64) && \+#if ( \+ FOLLY_SSE_PREREQ(4, 2) || \+ (FOLLY_AARCH64 && FOLLY_F14_CRC_INTRINSIC_AVAILABLE)) && \ FOLLY_F14_VECTOR_INTRINSICS_AVAILABLE namespace simd {@@ -855,7 +899,7 @@ Atom, HazptrDeleter<Allocator>> { public:- typedef std::pair<const KeyType, ValueType> value_type;+ using value_type = std::pair<const KeyType, ValueType>; template <typename Arg, typename... Args> NodeT(hazptr_obj_cohort<Atom>* cohort, Arg&& k, Args&&... args)@@ -1082,35 +1126,36 @@ } }; - class Chunks : public hazptr_obj_base<Chunks, Atom, HazptrTableDeleter> {- Chunks() {}- ~Chunks() {}+ class Chunks+ : public hazptr_obj_base<Chunks, Atom, HazptrTableDeleter>,+ public AlignedStorage<Chunks, Chunk, Allocator> {+ Chunks() = default;+ ~Chunks() = default; public: static Chunks* create(size_t count, hazptr_obj_cohort<Atom>* cohort) {- auto buf = Allocator().allocate(sizeof(Chunks) + sizeof(Chunk) * count);- auto chunks = new (buf) Chunks();+ auto* buf = Chunks::allocate(count);+ auto* chunks = new (buf) Chunks(); DCHECK(cohort); chunks->set_cohort_tag(cohort); // defined in hazptr_obj for (size_t i = 0; i < count; i++) {- new (&chunks->chunks_[i]) Chunk;- chunks->chunks_[i].clear();+ new (&chunks->array()[i]) Chunk;+ chunks->array()[i].clear(); } return chunks; } void destroy(size_t count) { for (size_t i = 0; i < count; i++) {- chunks_[i].~Chunk();+ Chunks::array()[i].~Chunk(); } this->~Chunks();- Allocator().deallocate(- (uint8_t*)this, sizeof(Chunk) * count + sizeof(*this));+ Chunks::deallocate(count); } void reclaim_nodes(size_t count) { for (size_t i = 0; i < count; i++) {- Chunk& chunk = chunks_[i];+ Chunk& chunk = Chunks::array()[i]; auto occupied = chunk.occupiedIter(); while (occupied.hasNext()) { auto idx = occupied.next();@@ -1126,18 +1171,15 @@ Chunk* getChunk(size_t index, size_t ccount) { DCHECK(isPowTwo(ccount));- return &chunks_[index & (ccount - 1)];+ return &Chunks::array()[index & (ccount - 1)]; }-- private:- Chunk chunks_[0]; }; public: static constexpr float kDefaultLoadFactor = Chunk::kDesiredCapacity / (float)Chunk::kCapacity; - typedef std::pair<const KeyType, ValueType> value_type;+ using value_type = std::pair<const KeyType, ValueType>; using InsertType = concurrenthashmap::InsertType; @@ -1732,6 +1774,14 @@ #endif // FOLLY_SSE_PREREQ(4, 2) && FOLLY_F14_VECTOR_INTRINSICS_AVAILABLE +template <typename ValueType>+struct EqualTo1 {+ const ValueType& rhs;+ bool operator()(const ValueType& lhs) const { return lhs == rhs; }+};+template <typename ValueType>+EqualTo1(const ValueType&) -> EqualTo1<ValueType>;+ } // namespace concurrenthashmap /* A Segment is a single shard of the ConcurrentHashMap.@@ -1773,10 +1823,8 @@ typename, typename, typename,- template <typename>- class,- class>- class Impl = concurrenthashmap::bucket::BucketTable>+ template <typename> class,+ class> class Impl = concurrenthashmap::bucket::BucketTable> class alignas(64) ConcurrentHashMapSegment { using ImplT = Impl< KeyType,@@ -1789,10 +1837,10 @@ Mutex>; public:- typedef KeyType key_type;- typedef ValueType mapped_type;- typedef std::pair<const KeyType, ValueType> value_type;- typedef std::size_t size_type;+ using key_type = KeyType;+ using mapped_type = ValueType;+ using value_type = std::pair<const KeyType, ValueType>;+ using size_type = std::size_t; using InsertType = concurrenthashmap::InsertType; using Iterator = typename ImplT::Iterator;@@ -1828,7 +1876,7 @@ h, g.node->getItem().first, InsertType::DOES_NOT_EXIST,- [](const ValueType&) { return false; },+ variadic_constant_of<false>, g.node); if (res) { g.dismiss();@@ -1845,7 +1893,7 @@ h, std::forward<Key>(k), InsertType::DOES_NOT_EXIST,- [](const ValueType&) { return false; },+ variadic_constant_of<false>, std::forward<Key>(k), std::forward<Args>(args)...); }@@ -1857,7 +1905,7 @@ h, k, InsertType::DOES_NOT_EXIST,- [](const ValueType&) { return false; },+ variadic_constant_of<false>, node); } @@ -1870,7 +1918,7 @@ h, g.node->getItem().first, InsertType::ANY,- [](const ValueType&) { return false; },+ variadic_constant_of<false>, g.node); if (res) { g.dismiss();@@ -1908,7 +1956,7 @@ h, g.node->getItem().first, InsertType::MUST_EXIST,- [](const ValueType&) { return false; },+ variadic_constant_of<false>, g.node); if (res) { g.dismiss();@@ -1949,7 +1997,7 @@ h, std::forward<Key>(k), std::forward<Value>(desired),- [&expected](const ValueType& v) { return v == expected; });+ concurrenthashmap::EqualTo1{expected}); } template <typename MatchFunc, typename K, typename... Args>@@ -1988,9 +2036,7 @@ // Listed separately because we need a prev pointer. template <typename K> size_type erase(size_t h, const K& key) {- return erase_internal(h, key, nullptr, [](const ValueType&) {- return true;- });+ return erase_internal(h, key, nullptr, variadic_constant_of<true>); } template <typename K, typename Predicate>@@ -2011,7 +2057,7 @@ // This is a small departure from standard stl containers: erase may // throw if hash or key_eq functions throw. void erase(Iterator& res, Iterator& pos, size_t h) {- erase_internal(h, pos->first, &res, [](const ValueType&) { return true; });+ erase_internal(h, pos->first, &res, variadic_constant_of<true>); // Invalidate the iterator. pos = cend(); }
@@ -187,7 +187,7 @@ return nullptr; } - // Waits until all the refereces obtained via lock() are released. Then+ // Waits until all the references obtained via lock() are released. Then // destroys the object in the current thread. // Can not be called concurrently with set(). void join() {@@ -197,7 +197,7 @@ this->cleanup().get(); } - // Returns: a SemiFuture that waits until all the refereces obtained via+ // Returns: a SemiFuture that waits until all the references obtained via // lock() are released. Then destroys the object on the Executor provided to // the SemiFuture. //@@ -211,7 +211,7 @@ .deferValue([this](folly::Unit) { if (!this->outerPtrShared_) { LOG(FATAL)- << "Cleanup already run - lock() was previouly disabled.";+ << "Cleanup already run - lock() was previously disabled."; } this->outerPtrShared_.reset(); return std::move(this->unreferenced_);
@@ -0,0 +1,67 @@+/*+ * Copyright (c) Meta Platforms, Inc. and affiliates.+ *+ * Licensed under the Apache License, Version 2.0 (the "License");+ * you may not use this file except in compliance with the License.+ * You may obtain a copy of the License at+ *+ * http://www.apache.org/licenses/LICENSE-2.0+ *+ * Unless required by applicable law or agreed to in writing, software+ * distributed under the License is distributed on an "AS IS" BASIS,+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.+ * See the License for the specific language governing permissions and+ * limitations under the License.+ */++#pragma once++#include <algorithm>+#include <concepts>++namespace folly {++namespace detail {+template <typename T, typename K>+concept HasContains = requires(T& t, const K& k) {+ { t.contains(k) };+};++template <typename T, typename K>+concept HasFind = requires(T& t, const K& k) {+ { t.find(k) } -> std::convertible_to<typename T::const_iterator>;+};+} // namespace detail+/**+ * This function check whether container contains given value.+ * Can be used for non-associative containers.+ * Notes:+ * Runtime = O(n)+ * Work with vector, map.+ */+template <class C, class V = typename C::value_type>+ requires(!detail::HasContains<C, V> && !detail::HasFind<C, V>)+bool contains(const C& container, const V& value) {+ const auto e = std::end(container);+ return std::find(std::begin(container), e, value) != e;+}++/**+ * This function checks whether container contains given key.+ * Use container specific .contains() implementation if available,+ * otherwise uses .find() implementation if available, otherwise+ * fallback to O(n) std::find() implementation.+ */+template <class C, class K = typename C::key_type>+bool contains(const C& container, const K& key) {+ if constexpr (detail::HasContains<C, K>) {+ return container.contains(key);+ } else if constexpr (detail::HasFind<C, K>) {+ return container.find(key) != container.end();+ } else {+ // Fallback: use generic and possibly slower std::find otherwise.+ return contains(container, key);+ }+}++} // namespace folly
@@ -49,7 +49,7 @@ * NOTE: maxSize==0 is a special case that disables automatic evictions. * prune() can be used for manually trimming down the number of entries. *- * Implementaion: Maintains a doubly linked list (`lru_`) of entry nodes in+ * Implementation: Maintains a doubly linked list (`lru_`) of entry nodes in * LRU order, which are also connected to hash table index (`index_`). The * access order is maintained on the list by moving an element to the front * of list on a get, and adding to the front on insert. Assuming quality@@ -168,7 +168,7 @@ keyEqual_(keyEqual), index_(maxSize + /*transient*/ 1, keyHash_, keyEqual_), maxSize_(maxSize),- clearSize_(clearSize) {}+ clearSize_(clampClearSize(clearSize)) {} EvictingCacheMap(const EvictingCacheMap&) = delete; EvictingCacheMap& operator=(const EvictingCacheMap&) = delete;@@ -181,7 +181,7 @@ * Adjust the max size of EvictingCacheMap, evicting as needed to ensure the * new max is not exceeded. *- * Calling this function with an arugment of 0 removes the limit on the cache+ * Calling this function with an argument of 0 removes the limit on the cache * size and elements are not evicted unless clients explicitly call prune. * * @param maxSize new maximum size of the cache map.@@ -197,7 +197,9 @@ std::size_t getMaxSize() const { return maxSize_; } - void setClearSize(std::size_t clearSize) { clearSize_ = clearSize; }+ void setClearSize(std::size_t clearSize) {+ clearSize_ = clampClearSize(clearSize);+ } /** * Check for existence of a specific key in the map. This operation has@@ -735,6 +737,12 @@ } else { return nullptr; }+ }++ // A zero clear size doesn't make sense. If you want to disable clearing, set+ // maxSize to 0.+ static std::size_t clampClearSize(std::size_t clearSize) {+ return std::max(clearSize, std::size_t{1}); } PruneHookCall pruneHook_;
@@ -1154,13 +1154,12 @@ typename Alloc, typename = detail::RequireInputIterator<InputIt>, typename = detail::RequireAllocator<Alloc>>-F14ValueMap(InputIt, InputIt, std::size_t, Alloc)- -> F14ValueMap<- iterator_key_type_t<InputIt>,- iterator_mapped_type_t<InputIt>,- f14::DefaultHasher<iterator_key_type_t<InputIt>>,- f14::DefaultKeyEqual<iterator_key_type_t<InputIt>>,- Alloc>;+F14ValueMap(InputIt, InputIt, std::size_t, Alloc) -> F14ValueMap<+ iterator_key_type_t<InputIt>,+ iterator_mapped_type_t<InputIt>,+ f14::DefaultHasher<iterator_key_type_t<InputIt>>,+ f14::DefaultKeyEqual<iterator_key_type_t<InputIt>>,+ Alloc>; template < typename InputIt,@@ -1169,13 +1168,12 @@ typename = detail::RequireInputIterator<InputIt>, typename = detail::RequireNotAllocator<Hasher>, typename = detail::RequireAllocator<Alloc>>-F14ValueMap(InputIt, InputIt, std::size_t, Hasher, Alloc)- -> F14ValueMap<- iterator_key_type_t<InputIt>,- iterator_mapped_type_t<InputIt>,- Hasher,- f14::DefaultKeyEqual<iterator_key_type_t<InputIt>>,- Alloc>;+F14ValueMap(InputIt, InputIt, std::size_t, Hasher, Alloc) -> F14ValueMap<+ iterator_key_type_t<InputIt>,+ iterator_mapped_type_t<InputIt>,+ Hasher,+ f14::DefaultKeyEqual<iterator_key_type_t<InputIt>>,+ Alloc>; template < typename Key,@@ -1304,13 +1302,12 @@ typename Alloc, typename = detail::RequireInputIterator<InputIt>, typename = detail::RequireAllocator<Alloc>>-F14NodeMap(InputIt, InputIt, std::size_t, Alloc)- -> F14NodeMap<- iterator_key_type_t<InputIt>,- iterator_mapped_type_t<InputIt>,- f14::DefaultHasher<iterator_key_type_t<InputIt>>,- f14::DefaultKeyEqual<iterator_key_type_t<InputIt>>,- Alloc>;+F14NodeMap(InputIt, InputIt, std::size_t, Alloc) -> F14NodeMap<+ iterator_key_type_t<InputIt>,+ iterator_mapped_type_t<InputIt>,+ f14::DefaultHasher<iterator_key_type_t<InputIt>>,+ f14::DefaultKeyEqual<iterator_key_type_t<InputIt>>,+ Alloc>; template < typename InputIt,@@ -1319,13 +1316,12 @@ typename = detail::RequireInputIterator<InputIt>, typename = detail::RequireNotAllocator<Hasher>, typename = detail::RequireAllocator<Alloc>>-F14NodeMap(InputIt, InputIt, std::size_t, Hasher, Alloc)- -> F14NodeMap<- iterator_key_type_t<InputIt>,- iterator_mapped_type_t<InputIt>,- Hasher,- f14::DefaultKeyEqual<iterator_key_type_t<InputIt>>,- Alloc>;+F14NodeMap(InputIt, InputIt, std::size_t, Hasher, Alloc) -> F14NodeMap<+ iterator_key_type_t<InputIt>,+ iterator_mapped_type_t<InputIt>,+ Hasher,+ f14::DefaultKeyEqual<iterator_key_type_t<InputIt>>,+ Alloc>; template < typename Key,@@ -1715,13 +1711,12 @@ typename Alloc, typename = detail::RequireInputIterator<InputIt>, typename = detail::RequireAllocator<Alloc>>-F14VectorMap(InputIt, InputIt, std::size_t, Alloc)- -> F14VectorMap<- iterator_key_type_t<InputIt>,- iterator_mapped_type_t<InputIt>,- f14::DefaultHasher<iterator_key_type_t<InputIt>>,- f14::DefaultKeyEqual<iterator_key_type_t<InputIt>>,- Alloc>;+F14VectorMap(InputIt, InputIt, std::size_t, Alloc) -> F14VectorMap<+ iterator_key_type_t<InputIt>,+ iterator_mapped_type_t<InputIt>,+ f14::DefaultHasher<iterator_key_type_t<InputIt>>,+ f14::DefaultKeyEqual<iterator_key_type_t<InputIt>>,+ Alloc>; template < typename InputIt,@@ -1730,13 +1725,12 @@ typename = detail::RequireInputIterator<InputIt>, typename = detail::RequireNotAllocator<Hasher>, typename = detail::RequireAllocator<Alloc>>-F14VectorMap(InputIt, InputIt, std::size_t, Hasher, Alloc)- -> F14VectorMap<- iterator_key_type_t<InputIt>,- iterator_mapped_type_t<InputIt>,- Hasher,- f14::DefaultKeyEqual<iterator_key_type_t<InputIt>>,- Alloc>;+F14VectorMap(InputIt, InputIt, std::size_t, Hasher, Alloc) -> F14VectorMap<+ iterator_key_type_t<InputIt>,+ iterator_mapped_type_t<InputIt>,+ Hasher,+ f14::DefaultKeyEqual<iterator_key_type_t<InputIt>>,+ Alloc>; template < typename Key,@@ -1853,13 +1847,12 @@ typename Alloc, typename = detail::RequireInputIterator<InputIt>, typename = detail::RequireAllocator<Alloc>>-F14FastMap(InputIt, InputIt, std::size_t, Alloc)- -> F14FastMap<- iterator_key_type_t<InputIt>,- iterator_mapped_type_t<InputIt>,- f14::DefaultHasher<iterator_key_type_t<InputIt>>,- f14::DefaultKeyEqual<iterator_key_type_t<InputIt>>,- Alloc>;+F14FastMap(InputIt, InputIt, std::size_t, Alloc) -> F14FastMap<+ iterator_key_type_t<InputIt>,+ iterator_mapped_type_t<InputIt>,+ f14::DefaultHasher<iterator_key_type_t<InputIt>>,+ f14::DefaultKeyEqual<iterator_key_type_t<InputIt>>,+ Alloc>; template < typename InputIt,@@ -1868,13 +1861,12 @@ typename = detail::RequireInputIterator<InputIt>, typename = detail::RequireNotAllocator<Hasher>, typename = detail::RequireAllocator<Alloc>>-F14FastMap(InputIt, InputIt, std::size_t, Hasher, Alloc)- -> F14FastMap<- iterator_key_type_t<InputIt>,- iterator_mapped_type_t<InputIt>,- Hasher,- f14::DefaultKeyEqual<iterator_key_type_t<InputIt>>,- Alloc>;+F14FastMap(InputIt, InputIt, std::size_t, Hasher, Alloc) -> F14FastMap<+ iterator_key_type_t<InputIt>,+ iterator_mapped_type_t<InputIt>,+ Hasher,+ f14::DefaultKeyEqual<iterator_key_type_t<InputIt>>,+ Alloc>; template < typename Key,
@@ -873,12 +873,11 @@ typename Alloc, typename = detail::RequireInputIterator<InputIt>, typename = detail::RequireAllocator<Alloc>>-F14ValueSet(InputIt, InputIt, std::size_t, Alloc)- -> F14ValueSet<- iterator_value_type_t<InputIt>,- f14::DefaultHasher<iterator_value_type_t<InputIt>>,- f14::DefaultKeyEqual<iterator_value_type_t<InputIt>>,- Alloc>;+F14ValueSet(InputIt, InputIt, std::size_t, Alloc) -> F14ValueSet<+ iterator_value_type_t<InputIt>,+ f14::DefaultHasher<iterator_value_type_t<InputIt>>,+ f14::DefaultKeyEqual<iterator_value_type_t<InputIt>>,+ Alloc>; template < typename InputIt,@@ -887,12 +886,11 @@ typename = detail::RequireInputIterator<InputIt>, typename = detail::RequireNotAllocator<Hasher>, typename = detail::RequireAllocator<Alloc>>-F14ValueSet(InputIt, InputIt, std::size_t, Hasher, Alloc)- -> F14ValueSet<- iterator_value_type_t<InputIt>,- Hasher,- f14::DefaultKeyEqual<iterator_value_type_t<InputIt>>,- Alloc>;+F14ValueSet(InputIt, InputIt, std::size_t, Hasher, Alloc) -> F14ValueSet<+ iterator_value_type_t<InputIt>,+ Hasher,+ f14::DefaultKeyEqual<iterator_value_type_t<InputIt>>,+ Alloc>; template < typename Key,@@ -913,12 +911,11 @@ typename Key, typename Alloc, typename = detail::RequireAllocator<Alloc>>-F14ValueSet(std::initializer_list<Key>, std::size_t, Alloc)- -> F14ValueSet<- Key,- f14::DefaultHasher<Key>,- f14::DefaultKeyEqual<Key>,- Alloc>;+F14ValueSet(std::initializer_list<Key>, std::size_t, Alloc) -> F14ValueSet<+ Key,+ f14::DefaultHasher<Key>,+ f14::DefaultKeyEqual<Key>,+ Alloc>; template < typename Key,@@ -992,12 +989,11 @@ typename Alloc, typename = detail::RequireInputIterator<InputIt>, typename = detail::RequireAllocator<Alloc>>-F14NodeSet(InputIt, InputIt, std::size_t, Alloc)- -> F14NodeSet<- iterator_value_type_t<InputIt>,- f14::DefaultHasher<iterator_value_type_t<InputIt>>,- f14::DefaultKeyEqual<iterator_value_type_t<InputIt>>,- Alloc>;+F14NodeSet(InputIt, InputIt, std::size_t, Alloc) -> F14NodeSet<+ iterator_value_type_t<InputIt>,+ f14::DefaultHasher<iterator_value_type_t<InputIt>>,+ f14::DefaultKeyEqual<iterator_value_type_t<InputIt>>,+ Alloc>; template < typename InputIt,@@ -1006,12 +1002,11 @@ typename = detail::RequireInputIterator<InputIt>, typename = detail::RequireNotAllocator<Hasher>, typename = detail::RequireAllocator<Alloc>>-F14NodeSet(InputIt, InputIt, std::size_t, Hasher, Alloc)- -> F14NodeSet<- iterator_value_type_t<InputIt>,- Hasher,- f14::DefaultKeyEqual<iterator_value_type_t<InputIt>>,- Alloc>;+F14NodeSet(InputIt, InputIt, std::size_t, Hasher, Alloc) -> F14NodeSet<+ iterator_value_type_t<InputIt>,+ Hasher,+ f14::DefaultKeyEqual<iterator_value_type_t<InputIt>>,+ Alloc>; template < typename Key,@@ -1032,12 +1027,11 @@ typename Key, typename Alloc, typename = detail::RequireAllocator<Alloc>>-F14NodeSet(std::initializer_list<Key>, std::size_t, Alloc)- -> F14NodeSet<- Key,- f14::DefaultHasher<Key>,- f14::DefaultKeyEqual<Key>,- Alloc>;+F14NodeSet(std::initializer_list<Key>, std::size_t, Alloc) -> F14NodeSet<+ Key,+ f14::DefaultHasher<Key>,+ f14::DefaultKeyEqual<Key>,+ Alloc>; template < typename Key,@@ -1312,12 +1306,11 @@ typename Alloc, typename = detail::RequireInputIterator<InputIt>, typename = detail::RequireAllocator<Alloc>>-F14VectorSet(InputIt, InputIt, std::size_t, Alloc)- -> F14VectorSet<- iterator_value_type_t<InputIt>,- f14::DefaultHasher<iterator_value_type_t<InputIt>>,- f14::DefaultKeyEqual<iterator_value_type_t<InputIt>>,- Alloc>;+F14VectorSet(InputIt, InputIt, std::size_t, Alloc) -> F14VectorSet<+ iterator_value_type_t<InputIt>,+ f14::DefaultHasher<iterator_value_type_t<InputIt>>,+ f14::DefaultKeyEqual<iterator_value_type_t<InputIt>>,+ Alloc>; template < typename InputIt,@@ -1326,12 +1319,11 @@ typename = detail::RequireInputIterator<InputIt>, typename = detail::RequireNotAllocator<Hasher>, typename = detail::RequireAllocator<Alloc>>-F14VectorSet(InputIt, InputIt, std::size_t, Hasher, Alloc)- -> F14VectorSet<- iterator_value_type_t<InputIt>,- Hasher,- f14::DefaultKeyEqual<iterator_value_type_t<InputIt>>,- Alloc>;+F14VectorSet(InputIt, InputIt, std::size_t, Hasher, Alloc) -> F14VectorSet<+ iterator_value_type_t<InputIt>,+ Hasher,+ f14::DefaultKeyEqual<iterator_value_type_t<InputIt>>,+ Alloc>; template < typename Key,@@ -1352,12 +1344,11 @@ typename Key, typename Alloc, typename = detail::RequireAllocator<Alloc>>-F14VectorSet(std::initializer_list<Key>, std::size_t, Alloc)- -> F14VectorSet<- Key,- f14::DefaultHasher<Key>,- f14::DefaultKeyEqual<Key>,- Alloc>;+F14VectorSet(std::initializer_list<Key>, std::size_t, Alloc) -> F14VectorSet<+ Key,+ f14::DefaultHasher<Key>,+ f14::DefaultKeyEqual<Key>,+ Alloc>; template < typename Key,@@ -1417,12 +1408,11 @@ typename Alloc, typename = detail::RequireInputIterator<InputIt>, typename = detail::RequireAllocator<Alloc>>-F14FastSet(InputIt, InputIt, std::size_t, Alloc)- -> F14FastSet<- iterator_value_type_t<InputIt>,- f14::DefaultHasher<iterator_value_type_t<InputIt>>,- f14::DefaultKeyEqual<iterator_value_type_t<InputIt>>,- Alloc>;+F14FastSet(InputIt, InputIt, std::size_t, Alloc) -> F14FastSet<+ iterator_value_type_t<InputIt>,+ f14::DefaultHasher<iterator_value_type_t<InputIt>>,+ f14::DefaultKeyEqual<iterator_value_type_t<InputIt>>,+ Alloc>; template < typename InputIt,@@ -1431,12 +1421,11 @@ typename = detail::RequireInputIterator<InputIt>, typename = detail::RequireNotAllocator<Hasher>, typename = detail::RequireAllocator<Alloc>>-F14FastSet(InputIt, InputIt, std::size_t, Hasher, Alloc)- -> F14FastSet<- iterator_value_type_t<InputIt>,- Hasher,- f14::DefaultKeyEqual<iterator_value_type_t<InputIt>>,- Alloc>;+F14FastSet(InputIt, InputIt, std::size_t, Hasher, Alloc) -> F14FastSet<+ iterator_value_type_t<InputIt>,+ Hasher,+ f14::DefaultKeyEqual<iterator_value_type_t<InputIt>>,+ Alloc>; template < typename Key,@@ -1457,12 +1446,11 @@ typename Key, typename Alloc, typename = detail::RequireAllocator<Alloc>>-F14FastSet(std::initializer_list<Key>, std::size_t, Alloc)- -> F14FastSet<- Key,- f14::DefaultHasher<Key>,- f14::DefaultKeyEqual<Key>,- Alloc>;+F14FastSet(std::initializer_list<Key>, std::size_t, Alloc) -> F14FastSet<+ Key,+ f14::DefaultHasher<Key>,+ f14::DefaultKeyEqual<Key>,+ Alloc>; template < typename Key,
@@ -610,7 +610,7 @@ // Destroys the old data. // // 3: relocate_undo- // Destoys the new data and restores the old data.+ // Destroys the new data and restores the old data. // // The three steps are used because there may be an exception after part 1 // has completed. If that is the case, then relocate_undo can nullify the
@@ -24,6 +24,7 @@ #include <folly/Traits.h> #include <folly/container/HeterogeneousAccess-fwd.h> #include <folly/hash/Hash.h>+#include <folly/hash/detail/RandomSeed.h> #include <folly/hash/rapidhash.h> namespace folly {@@ -48,6 +49,10 @@ // Additional specializations of HeterogeneousAccess*<T> should go in // the header that declares T. Don't forget to typedef is_transparent to // void and folly_is_avalanching to std::true_type in the specializations.+//+// Hash values of folly::HeterogeneousAccessHash are not guaranteed to be+// stable across different program runs, so do not persist hash values+// anywhere. template <typename T, typename Enable> struct HeterogeneousAccessEqualTo : std::equal_to<T> {};@@ -87,6 +92,10 @@ struct TransparentRangeEqualTo { using is_transparent = void; + template <typename TOtherHash>+ using is_compatible =+ std::is_base_of<detail::TransparentRangeEqualTo<T>, TOtherHash>;+ template <typename U1, typename U2> bool operator()(U1 const& lhs, U2 const& rhs) const { return Range<T const*>{lhs} == Range<T const*>{rhs};@@ -105,6 +114,10 @@ using is_transparent = void; using folly_is_avalanching = std::true_type; + template <typename TOtherHash>+ using is_compatible =+ std::is_base_of<detail::TransparentRangeHash<T>, TOtherHash>;+ template <typename U> std::size_t operator()(U const& stringish) const { return hasher<Range<T const*>>{}(Range<T const*>{stringish});@@ -116,11 +129,15 @@ using is_transparent = void; using folly_is_avalanching = std::true_type; + template <typename TOtherHash>+ using is_compatible =+ std::is_base_of<detail::TransparentRangeHash<char>, TOtherHash>;+ template <typename U> std::size_t operator()(U const& stringish) const { auto sp = StringPiece{stringish};- return static_cast<std::size_t>(- folly::hash::rapidhashNano(sp.data(), sp.size()));+ return static_cast<std::size_t>(folly::hash::rapidhashNano_with_seed(+ sp.data(), sp.size(), hash::detail::RandomSeed::seed())); } }; @@ -145,6 +162,15 @@ EligibleForHeterogeneousFind<TableKey, Hasher, KeyEqual, ArgKey>, std::is_constructible<TableKey, ArgKey>>; +template <typename T, typename OtherT, typename Enable = void>+struct HasCompatibleTest : std::false_type {};++template <typename T, typename OtherT>+struct HasCompatibleTest<+ T,+ OtherT,+ void_t<typename T::template is_compatible<OtherT>>> : std::true_type {};+ } // namespace detail template <typename T>@@ -162,5 +188,37 @@ : detail::TransparentRangeHash< typename detail::ValueTypeForTransparentConversionToRange<T>::type> { };++template <typename Hash1, typename Hash2, typename Enable = void>+struct HeterogeneousPreHashCompatible : std::is_same<Hash1, Hash2> {};++template <typename Hash1, typename Hash2>+struct HeterogeneousPreHashCompatible<+ Hash1,+ Hash2,+ std::enable_if_t<+ detail::HasCompatibleTest<Hash1, Hash2>::value &&+ detail::HasCompatibleTest<Hash2, Hash1>::value>>+ : Disjunction<+ typename Hash1::template is_compatible<Hash2>,+ typename Hash2::template is_compatible<Hash1>> {};++template <typename Hash1, typename Hash2>+struct HeterogeneousPreHashCompatible<+ Hash1,+ Hash2,+ std::enable_if_t<+ detail::HasCompatibleTest<Hash1, Hash2>::value &&+ !detail::HasCompatibleTest<Hash2, Hash1>::value>>+ : Hash1::template is_compatible<Hash2> {};++template <typename Hash1, typename Hash2>+struct HeterogeneousPreHashCompatible<+ Hash1,+ Hash2,+ std::enable_if_t<+ !detail::HasCompatibleTest<Hash1, Hash2>::value &&+ detail::HasCompatibleTest<Hash2, Hash1>::value>>+ : Hash2::template is_compatible<Hash1> {}; } // namespace folly
@@ -31,6 +31,7 @@ #include <boost/noncopyable.hpp> #include <glog/logging.h> #include <folly/Portability.h>+#include <folly/lang/Builtin.h> namespace folly { @@ -74,7 +75,7 @@ static T* asT(IntrusiveHeapNode<Tag>* n) { return static_cast<T*>(n); } }; -template <class T, class Tag, IntrusiveHeapNode<Tag> T::*PtrToMember>+template <class T, class Tag, IntrusiveHeapNode<Tag> T::* PtrToMember> struct MemberNodeTraits { static IntrusiveHeapNode<Tag>* asNode(T* x) { return &(x->*PtrToMember); } static T* asT(IntrusiveHeapNode<Tag>* n) {@@ -229,7 +230,7 @@ } do { Node* grandparent = parent;- if (compare(a, b)) {+ if (FOLLY_BUILTIN_UNPREDICTABLE(compare(a, b))) { parent = b; } else { parent = a;
@@ -61,7 +61,7 @@ * The elements stored in the list must contain an IntrusiveListHook member * variable. */-template <typename T, IntrusiveListHook T::*PtrToMember>+template <typename T, IntrusiveListHook T::* PtrToMember> using IntrusiveList = boost::intrusive::list< T, boost::intrusive::member_hook<T, IntrusiveListHook, PtrToMember>,@@ -80,7 +80,7 @@ * that the hook is initialised to an unlinked state on construction and reset * an unlinked state upon removing it from a list. */-template <typename T, SafeIntrusiveListHook T::*PtrToMember>+template <typename T, SafeIntrusiveListHook T::* PtrToMember> using SafeIntrusiveList = boost::intrusive::list< T, boost::intrusive::member_hook<T, SafeIntrusiveListHook, PtrToMember>,@@ -122,7 +122,7 @@ * The elements stored in the list must contain an SafeIntrusiveListHook member * variable. */-template <typename T, SafeIntrusiveListHook T::*PtrToMember>+template <typename T, SafeIntrusiveListHook T::* PtrToMember> using CountedIntrusiveList = boost::intrusive::list< T, boost::intrusive::member_hook<T, SafeIntrusiveListHook, PtrToMember>,
@@ -472,8 +472,7 @@ * aliases cannot be used for CRTP. */ template <- template <typename>- class EmplaceImplT,+ template <typename> class EmplaceImplT, typename Container, bool implicit_unpack> class emplace_iterator_impl
@@ -125,6 +125,20 @@ } /**+ * Given a map and a key, return a OptionalValue if the key exists and None if+ * the key does not exist in the map.+ */+template <class OptionalValue, class Map, typename Key = typename Map::key_type>+OptionalValue get_optional(const Map& map, const Key& key) {+ auto pos = map.find(key);+ if (pos != map.end()) {+ return OptionalValue(pos->second);+ } else {+ return {};+ }+}++/** * Given a map and a key, return a reference to the value corresponding to the * key in the map, or the given default reference if the key doesn't exist in * the map.@@ -327,9 +341,8 @@ Map* FOLLY_NULLABLE map, const Key1& key1, const Key2& key2,- const Keys&... keys) ->- typename detail::NestedMapType<Map, 2 + sizeof...(Keys)>::- type* FOLLY_NULLABLE {+ const Keys&... keys) -> typename detail::+ NestedMapType<Map, 2 + sizeof...(Keys)>::type* FOLLY_NULLABLE { return map ? get_ptr(*map, key1, key2, keys...) : nullptr; }
@@ -15,7 +15,7 @@ */ /*- * folly::merge() is an implementation of std::merge with one additonal+ * folly::merge() is an implementation of std::merge with one additional * guarantee: if the input ranges overlap, the order that values *from the two * different ranges* appear in the output is well defined (std::merge only * guarantees relative ordering is maintained within a single input range).
@@ -27,7 +27,6 @@ #include <folly/MapUtil.h> #include <folly/String.h> #include <folly/container/Reserve.h>-#include <folly/ssl/OpenSSLHash.h> #include <folly/synchronization/AtomicUtil.h> namespace folly {@@ -36,13 +35,6 @@ return fmt::format("\"{}\"", cEscape<std::string>(s)); } -RegexMatchCacheKey::data_type RegexMatchCacheKey::init(- std::string_view const regex) noexcept {- data_type data;- folly::ssl::OpenSSLHash::sha256(range(data), StringPiece(regex));- return data;-}- class RegexMatchCache::RegexObject { private: boost::regex object;@@ -156,7 +148,7 @@ auto const q = [](std::string_view const s) { return quote(s); }; auto const h = report; - auto const r = [&](regex_key const r) { return keys.lookup(r); };+ auto const k = [&](regex_key const r) { return keys.lookup(r); }; if (cacheRegexToMatch_.empty() || cacheMatchToRegex_.empty()) { if (!stringQueueForward_.empty()) {@@ -171,7 +163,7 @@ // check that caches are bidi-consistent // check that missing cache entries are found in string-queues for (auto const& [regex, rtmentry] : cacheRegexToMatch_) {- auto const regexs = r(regex);+ auto const regexs = k(regex); auto const regexi = regexVector_.index_of_value(®ex); for (auto const& [match, mtrentry] : cacheMatchToRegex_) { auto const rtmcontains = rtmentry.matches.contains(match);@@ -243,11 +235,11 @@ if (!sqrptr) { h(fmt::format( // "string-queue-reverse none regex[{}]",- q(r(*regex))));+ q(k(*regex)))); } else if (!sqrptr->strings.contains(string)) { h(fmt::format( // "string-queue-reverse[{}] none string[{}]",- q(r(*regex)),+ q(k(*regex)), q(*string))); } auto const mtrhas = mtrptr && mtrptr->regexes.get_value(regexi);@@ -257,7 +249,7 @@ h(fmt::format( // "string-queue-forward[{}] has regex[{}]", q(*string),- q(r(*regex))));+ q(k(*regex)))); } } }@@ -274,7 +266,7 @@ h(fmt::format( // "string-queue-forward[{}] none regex[{}]", q(*string),- q(r(*regex))));+ q(k(*regex)))); } auto const mtrptr = get_ptr(cacheMatchToRegex_, string); auto const mtrhas = mtrptr && mtrptr->regexes.get_value(regexi);@@ -282,7 +274,7 @@ if (mtrhas || rtmhas) { h(fmt::format( // "string-queue-reverse[{}] has string[{}]",- q(r(*regex)),+ q(k(*regex)), q(*string))); } }
@@ -32,7 +32,7 @@ #include <folly/container/F14Map.h> #include <folly/container/F14Set.h> #include <folly/container/Reserve.h>-#include <folly/container/span.h>+#include <folly/hash/UniqueHashKey.h> #include <folly/lang/Bits.h> namespace folly {@@ -373,6 +373,8 @@ folly::F14FastMap<size_t, Value> reverse_; }; +using RegexMatchCacheKeyBase = unique_hash_key_strong_sha256<32>;+ /// RegexMatchCacheKey /// /// A key derived from a string. Used with RegexMatchCache.@@ -380,47 +382,10 @@ /// Intended for use only with RegexMatchCache. /// /// Incomplete as a generic facility.-class RegexMatchCacheKey {- private:- using self = RegexMatchCacheKey;-- static inline constexpr size_t data_size = 32;- static inline constexpr size_t data_align = alignof(size_t);-- using data_type = std::array<unsigned char, data_size>;-- alignas(data_align) data_type const data_;-- static data_type init(std::string_view regex) noexcept;-- template <typename T, size_t E, typename V = std::remove_cv_t<T>>- static constexpr bool is_span_compatible_v = //- !std::is_volatile_v<T> && //- std::is_integral_v<V> && //- std::is_unsigned_v<V> && //- !std::is_same_v<bool, V> && //- !std::is_same_v<char, V> && //- alignof(V) <= data_align && //- (E == data_size / sizeof(T) || E == dynamic_extent);-+class RegexMatchCacheKey : public RegexMatchCacheKeyBase { public: explicit RegexMatchCacheKey(std::string_view regex) noexcept- : data_{init(regex)} {}-- template <- typename T,- std::size_t E,- std::enable_if_t<is_span_compatible_v<T, E>, int> = 0>- explicit operator span<T const, E>() const noexcept {- return {reinterpret_cast<T const*>(data_.data()), E};- }-- friend auto operator==(self const& a, self const& b) noexcept {- return a.data_ == b.data_;- }- friend auto operator!=(self const& a, self const& b) noexcept {- return a.data_ != b.data_;- }+ : RegexMatchCacheKeyBase{std::tuple(regex)} {} }; } // namespace folly@@ -428,13 +393,8 @@ namespace std { template <>-struct hash<::folly::RegexMatchCacheKey> {- using folly_is_avalanching = std::true_type;-- size_t operator()(::folly::RegexMatchCacheKey const& key) const noexcept {- return ::folly::span<size_t const>{key}[0];- }-};+struct hash<::folly::RegexMatchCacheKey>+ : hash<::folly::RegexMatchCacheKeyBase> {}; } // namespace std
@@ -19,24 +19,92 @@ #include <folly/CPortability.h> #include <folly/Portability.h> +#include <bit> #include <bitset> #include <cassert> namespace folly { +#ifdef _LIBCPP_VERSION++constexpr size_t kWordSize = 64;++namespace libcpp_detail {+struct std_bit_reference_layout {+ size_t* address;+ size_t mask;+};++// Return the content of the 64-bit word, given an index.+template <size_t N>+size_t* get_underlying_data_region(const std::bitset<N>& bitset) {+ // You are only getting the __bit_reference as the return if you access+ // with operator[], which is not const.+ auto& bitset_ref = const_cast<std::bitset<N>&>(bitset);+ std::__bit_reference bitref = bitset_ref[0];+ auto* br =+ reinterpret_cast<std_bit_reference_layout*>(std::addressof(bitref));+ return br->address;+}++template <size_t N>+size_t std_bitset_find_next(const std::bitset<N>& bitset, size_t start) {+ constexpr size_t max_words = N / kWordSize + 1;+ if constexpr (N == 0) {+ return 1;+ }++ // Check starting in the middle of a word+ size_t start_in_word = start % kWordSize;+ size_t word_idx = start / kWordSize;+ size_t* data = get_underlying_data_region(bitset);++ if (start_in_word) {+ // Invariant: start != 0, 1 <= start_in_word <= 63+ // test the remaining bits in the same word as previous+ size_t mask = (~size_t(0)) << start_in_word;+ size_t partial_word = data[word_idx] & mask;++ size_t idx_in_word = std::countr_zero(partial_word);+ if (idx_in_word != kWordSize) {+ // We found the next+ return start - start_in_word + idx_in_word;+ }+ // Special treatment of the partial first word was done. Skip it in the+ // next loop for whole words.+ word_idx += 1;+ }++ while (word_idx < max_words) {+ size_t word = data[word_idx];+ size_t idx_in_word = std::countr_zero(word);+ if (idx_in_word != kWordSize) {+ // we found the first+ return word_idx * kWordSize + idx_in_word;+ }+ word_idx += 1;+ }++ return N;+}+} // namespace libcpp_detail++#endif // _LIBCPP_VERSION+ struct std_bitset_find_first_fn { /// Return the index of the first set bit in a bitset, or bitset.size() if /// none. template <size_t N> FOLLY_ALWAYS_INLINE size_t operator()(const std::bitset<N>& bitset) const noexcept {- // equivalent to #if defined(__GLIBCXX__)- if constexpr (kIsGlibcxx) {- // GNU provides non-standard (its a hold over from the original SGI- // implementation) _Find_first(), which efficiently returns the index of- // the first set bit.- return bitset._Find_first();- }+#if defined(__GLIBCXX__)+ // GNU provides non-standard (its a hold over from the original SGI+ // implementation) _Find_first(), which efficiently returns the index of+ // the first set bit.+ return bitset._Find_first();+#elif defined(_LIBCPP_VERSION)+ return folly::libcpp_detail::std_bitset_find_next(bitset, 0);+#endif for (size_t i = 0; i < bitset.size(); ++i) { if (bitset[i]) {@@ -58,13 +126,14 @@ operator()(const std::bitset<N>& bitset, size_t prev) const noexcept { assert(prev < bitset.size()); - // equivalent to #if defined(__GLIBCXX__)- if constexpr (kIsGlibcxx) {- // GNU provides non-standard (its a hold over from the original SGI- // implementation) _Find_next(), which given an index, efficiently returns- // the index of the first set bit after the index.- return bitset._Find_next(prev);- }+#if defined(__GLIBCXX__)+ // GNU provides non-standard (its a hold over from the original SGI+ // implementation) _Find_next(), which given an index, efficiently returns+ // the index of the first set bit after the index.+ return bitset._Find_next(prev);+#elif defined(_LIBCPP_VERSION)+ return folly::libcpp_detail::std_bitset_find_next(bitset, prev + 1);+#endif for (size_t i = prev + 1; i < bitset.size(); ++i) { if (bitset[i]) {
@@ -1487,8 +1487,13 @@ }; template <- template <typename, typename, typename, typename, typename, typename...>- class Policy,+ template <+ typename,+ typename,+ typename,+ typename,+ typename,+ typename...> class Policy, typename Key, typename Mapped, typename Hasher,@@ -1504,8 +1509,13 @@ Args...>; template <- template <typename, typename, typename, typename, typename, typename...>- class Policy,+ template <+ typename,+ typename,+ typename,+ typename,+ typename,+ typename...> class Policy, typename Key, typename Hasher, typename KeyEqual,
@@ -161,18 +161,11 @@ V value; }; -#else+#elif defined(__GLIBCXX__) -template <typename H>-struct StdIsFastHash : std::true_type {};-template <>-struct StdIsFastHash<std::hash<long double>> : std::false_type {};-template <typename... Args>-struct StdIsFastHash<std::hash<std::basic_string<Args...>>> : std::false_type {-};-template <typename... Args>-struct StdIsFastHash<std::hash<std::basic_string_view<Args...>>>- : std::false_type {};+template <typename K, typename H>+constexpr bool kStdNodeContainsHash =+ !std::__is_fast_hash<H>::value || !is_nothrow_invocable_v<H, K const&>; // mimic internal node of unordered containers in STL to estimate the size template <typename K, typename V, typename H, typename Enable = void>@@ -181,17 +174,20 @@ V value; }; template <typename K, typename V, typename H>-struct StdNodeReplica<- K,- V,- H,- std::enable_if_t<- !StdIsFastHash<H>::value || !is_nothrow_invocable_v<H, K>>> {+struct StdNodeReplica<K, V, H, std::enable_if_t<kStdNodeContainsHash<K, H>>> { void* next; V value; std::size_t hash; }; +#else++template <typename K, typename V, typename H>+struct StdNodeReplica {+ void* next;+ V value;+};+ #endif template <class Container, class Predicate>@@ -1742,8 +1738,8 @@ template <typename HKKey, typename HKHasher, typename HKEqual> HashPair computeHash( F14HashedKey<HKKey, HKHasher, HKEqual> const& hashedKey) const {- static_assert(std::is_same_v<HKHasher, Hasher>);- static_assert(std::is_same_v<HKEqual, KeyEqual>);+ static_assert(HeterogeneousPreHashCompatible<Hasher, HKHasher>::value);+ static_assert(HeterogeneousPreHashCompatible<HKEqual, KeyEqual>::value); return static_cast<HashPair>(hashedKey.getHashToken()); } @@ -1772,6 +1768,20 @@ template <typename K> FOLLY_ALWAYS_INLINE ItemIter find(K const& key) const {+ const auto sz = size();+ if (sz <= 1) {+ if (sz == 0) {+ return ItemIter{};+ }+ // There is no easy way to obtain a begin iterator with the current policy+ // design.+ // As a result, F14Vector* containers (kEnableItemIteration == false) do+ // not benefit from this optimization yet.+ if constexpr (kEnableItemIteration) {+ ItemIter beg = begin();+ return this->keyMatchesItem(key, beg.citem()) ? beg : ItemIter{};+ }+ } auto hp = computeHash(key); return findImpl(hp, key, Prefetch::ENABLED); }@@ -1856,6 +1866,12 @@ } } + [[FOLLY_ATTR_GNU_COLD]] FOLLY_NOINLINE static void eraseBlankCold(+ F14Table* self, ItemIter iter, HashPair hp) {+ self->eraseBlank(iter, hp);+ rethrow_current_exception();+ }+ void adjustSizeAndBeginBeforeErase(ItemIter iter) { sizeAndChunkShiftAndPackedBegin_.decrementSize(); if constexpr (kEnableItemIteration) {@@ -1872,13 +1888,15 @@ template <typename... Args> void insertAtBlank(ItemIter pos, HashPair hp, Args&&... args) {- try {- auto dst = pos.itemAddr();- this->constructValueAtItem(*this, dst, std::forward<Args>(args)...);- } catch (...) {- eraseBlank(pos, hp);- throw;- }+ auto dst = pos.itemAddr();+ catch_exception(+ [&] {+ this->constructValueAtItem(*this, dst, std::forward<Args>(args)...);+ },+ &eraseBlankCold,+ this,+ pos,+ hp); adjustSizeAndBeginAfterInsert(pos); } @@ -2102,6 +2120,13 @@ success = true; } + [[FOLLY_ATTR_GNU_COLD]] FOLLY_NOINLINE static void buildFromF14TableCatchCold(+ F14Table* self) {+ self->reset();+ F14LinkCheck<getF14IntrinsicsMode()>::check();+ rethrow_current_exception();+ }+ template <typename T> FOLLY_NOINLINE void buildFromF14Table(T&& src) { FOLLY_SAFE_DCHECK(bucket_count() == 0, "");@@ -2122,17 +2147,16 @@ } rehashImpl(0, 1, 0, ccas.first, ccas.second); - try {- if (chunkShift() == src.chunkShift()) {- directBuildFrom(std::forward<T>(src));- } else {- rehashBuildFrom(std::forward<T>(src));- }- } catch (...) {- reset();- F14LinkCheck<getF14IntrinsicsMode()>::check();- throw;- }+ catch_exception(+ [&] {+ if (chunkShift() == src.chunkShift()) {+ directBuildFrom(std::forward<T>(src));+ } else {+ rehashBuildFrom(std::forward<T>(src));+ }+ },+ &F14Table::buildFromF14TableCatchCold,+ this); } void maybeRehash(std::size_t desiredCapacity, bool attemptExact) {@@ -2227,7 +2251,9 @@ std::size_t newChunkCount; std::size_t newCapacityScale; std::tie(newChunkCount, newCapacityScale) = computeChunkCountAndScale(- desiredCapacity, /*attemptExact=*/true, kContinuousCapacity);+ desiredCapacity,+ /*continuousSingleChunkCapacity=*/true,+ kContinuousCapacity); auto newCapacity = computeCapacity(newChunkCount, newCapacityScale); auto newAllocSize = chunkAllocSize(newChunkCount, newCapacityScale); @@ -2625,11 +2651,9 @@ // force recycling of heap memory auto bc = bucket_count(); reset();- try {- reserveImpl(bc);- } catch (std::bad_alloc const&) {- // ASAN mode only, keep going- }+ catch_exception<std::bad_alloc const&>(+ [this, bc]() { reserveImpl(bc); },+ &folly::detail::thunk::noop<std::bad_alloc const&>); } else { clearImpl<false>(); }@@ -2823,8 +2847,9 @@ namespace test { inline void disableInsertOrderRandomization() { if constexpr (kIsLibrarySanitizeAddress || kIsDebug) {- detail::tlsPendingSafeInserts(static_cast<std::ptrdiff_t>(- (std::numeric_limits<std::size_t>::max)() / 2));+ detail::tlsPendingSafeInserts(+ static_cast<std::ptrdiff_t>(+ (std::numeric_limits<std::size_t>::max)() / 2)); } } } // namespace test
@@ -146,9 +146,9 @@ UsableAsKey<remove_cvref_t<Arg1>>::value, int> = 0> auto callWithExtractedKey(- Alloc&,+ Alloc& /*unused*/, Func&& f,- std::piecewise_construct_t,+ std::piecewise_construct_t /*unused*/, std::tuple<Arg1>&& first_args, std::tuple<Args2...>&& second_args) { // we found a usable key in the args :)@@ -171,7 +171,7 @@ auto callWithExtractedKey( Alloc& a, Func&& f,- std::piecewise_construct_t,+ std::piecewise_construct_t /*unused*/, std::tuple<Args1...>&& first_args, std::tuple<Args2...>&& second_args) { // we will need to materialize a temporary key :(@@ -266,7 +266,7 @@ std::is_same<remove_cvref_t<Arg>, KeyType>::value || UsableAsKey<remove_cvref_t<Arg>>::value, int> = 0>-auto callWithConstructedKey(Alloc&, Func&& f, Arg&& arg) {+auto callWithConstructedKey(Alloc& /*unused*/, Func&& f, Arg&& arg) { // we found a usable key in the args :) auto const& key = arg; return f(key, std::forward<Arg>(arg));
@@ -248,10 +248,12 @@ end++; std::function<bool(size_type, size_type)> calculateDistance = [&](size_type offset, size_type lb) {- if (offset > size)+ if (offset > size) { return false;- for (; offset <= size; offset <<= 1)+ }+ for (; offset <= size; offset <<= 1) { ;+ } offset >>= 1; for (; offset > lb; offset >>= 1) { if (offset == start) {@@ -835,8 +837,9 @@ assert(cont_ == rawIterator.cont_); size_type offset0 = ptr_ - cont_->begin(); size_type offset1 = rawIterator.ptr_ - cont_->begin();- if (offset1 == offset0)+ if (offset1 == offset0) { return 0;+ } return heap_vector_detail::distance(*cont_, offset1, offset0); } @@ -844,8 +847,9 @@ assert(cont_ == rawIterator.cont_); size_type offset0 = ptr_ - cont_->begin(); size_type offset1 = rawIterator.ptr_ - cont_->begin();- if (offset1 == offset0)+ if (offset1 == offset0) { return 0;+ } return heap_vector_detail::distance(*cont_, offset1, offset0); } @@ -1692,8 +1696,9 @@ return offset - 1; } offset = 2 * offset + lt;- if (offset > size)+ if (offset > size) { return size;+ } cur_k = self.m_.getKey(cont[offset - 1]); } return size;
@@ -0,0 +1,70 @@+/*+ * Copyright (c) Meta Platforms, Inc. and affiliates.+ *+ * Licensed under the Apache License, Version 2.0 (the "License");+ * you may not use this file except in compliance with the License.+ * You may obtain a copy of the License at+ *+ * http://www.apache.org/licenses/LICENSE-2.0+ *+ * Unless required by applicable law or agreed to in writing, software+ * distributed under the License is distributed on an "AS IS" BASIS,+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.+ * See the License for the specific language governing permissions and+ * limitations under the License.+ */++#pragma once++#include <algorithm>+#include <concepts>+#include <ranges>+#include <type_traits>++namespace folly {++namespace detail {+template <typename T>+concept IRangeNonBoolIntegral = std::integral<T> && !std::same_as<T, bool>;+}++/// Creates an integer range for the half-open interval [begin, end)+/// If end<=begin, then the range is empty.+/// The range has the type of the `end` integer; `begin` integer is+/// cast to this type.+///+/// Instead of+/// for (int i = 3; i < 10; ++i) {+/// use+/// for (const auto i : irange(3, 10)) {+template <+ detail::IRangeNonBoolIntegral Integer1,+ detail::IRangeNonBoolIntegral Integer2>+constexpr std::ranges::iota_view<+ std::common_type_t<Integer1, Integer2>,+ std::common_type_t<Integer1, Integer2>>+irange(Integer1 begin, Integer2 end) {+ // If end<=begin then the range is empty; we can achieve this effect by+ // choosing the larger of {begin, end} as the loop terminator+ using otype = std::common_type_t<Integer1, Integer2>;+ return {+ static_cast<otype>(begin),+ std::max(static_cast<otype>(begin), static_cast<otype>(end))};+}++/// Creates an integer range for the half-open interval [0, end)+///+/// Instead of+/// for (int i = 0; i < 10; ++i) {+/// use+/// for (const auto i : irange(10)) {+///+/// NOTE! This behaviour differs from `iota_view(N)` which iterates+/// starts at `N` and loops over the full range of valus of `N`'s data+/// type forever.+template <detail::IRangeNonBoolIntegral Integer>+constexpr std::ranges::iota_view<Integer, Integer> irange(Integer end) {+ return {Integer(), end};+}++} // namespace folly
@@ -140,6 +140,17 @@ namespace small_vector_detail { /*+ * Just because a type is trivially copyable doesn't mean we should copy+ * it. A copy constructor that is deleted is still considered trivial.+ *+ * If a type is not move constructible, it shouldn't be relocated. We+ * should avoid resizing in general.+ */+template <typename T>+inline constexpr bool should_trivially_copy =+ std::is_trivially_copyable_v<T> && std::is_move_constructible_v<T>;++/* * Move objects in memory to the right into some uninitialized memory, where * the region overlaps. Then call create() for each hole in reverse order. *@@ -150,7 +161,7 @@ * extra copies and moves for non-trivial types. */ template <class T, class Create>-typename std::enable_if<!std::is_trivially_copyable_v<T>>::type+typename std::enable_if<!should_trivially_copy<T>>::type moveObjectsRightAndCreate( T* const first, T* const lastConstructed,@@ -204,7 +215,7 @@ // memory may be uninitialized, and std::move_backward() won't work when it // can't memmove(). template <class T, class Create>-typename std::enable_if<std::is_trivially_copyable_v<T>>::type+typename std::enable_if<should_trivially_copy<T>>::type moveObjectsRightAndCreate( T* const first, T* const lastConstructed,@@ -256,7 +267,7 @@ template <class SizeType, bool ShouldUseHeap, bool AlwaysUseHeap> struct IntegralSizePolicyBase {- typedef SizeType InternalSizeType;+ using InternalSizeType = SizeType; IntegralSizePolicyBase() : size_(0) {} @@ -342,7 +353,8 @@ * ranges don't overlap. */ template <class T>- typename std::enable_if<!std::is_trivially_copyable_v<T>>::type+ typename std::enable_if<+ !detail::small_vector_detail::should_trivially_copy<T>>::type moveToUninitialized(T* first, T* last, T* out) { std::size_t idx = 0; {@@ -363,7 +375,8 @@ // Specialization for trivially copyable types. template <class T>- typename std::enable_if<std::is_trivially_copyable_v<T>>::type+ typename std::enable_if<+ detail::small_vector_detail::should_trivially_copy<T>>::type moveToUninitialized(T* first, T* last, T* out) { std::memmove( static_cast<void*>(out),@@ -447,21 +460,19 @@ /* * Make the real policy base classes. */- typedef IntegralSizePolicy<+ using ActualSizePolicy = IntegralSizePolicy< typename Policy::size_type, !Policy::in_situ_only::value,- RequestedMaxInline == 0>- ActualSizePolicy;+ RequestedMaxInline == 0>; /* * Now inherit from them all. This is done in such a convoluted * way to make sure we get the empty base optimization on all these * types to keep sizeof(small_vector<>) minimal. */- typedef boost::totally_ordered1<+ using type = boost::totally_ordered1< small_vector<Value, RequestedMaxInline, InPolicy>,- ActualSizePolicy>- type;+ ActualSizePolicy>; }; namespace small_vector_detail {@@ -488,9 +499,9 @@ class small_vector : public detail::small_vector_base<Value, RequestedMaxInline, Policy>:: type {- typedef typename detail::- small_vector_base<Value, RequestedMaxInline, Policy>::type BaseType;- typedef typename BaseType::InternalSizeType InternalSizeType;+ using BaseType = typename detail::+ small_vector_base<Value, RequestedMaxInline, Policy>::type;+ using InternalSizeType = typename BaseType::InternalSizeType; /* * Figure out the max number of elements we should inline. (If@@ -505,19 +516,19 @@ : constexpr_max(kSizeOfValuePtr / kSizeOfValue, RequestedMaxInline)}; public:- typedef std::size_t size_type;- typedef Value value_type;- typedef std::allocator<Value> allocator_type;- typedef value_type& reference;- typedef value_type const& const_reference;- typedef value_type* iterator;- typedef value_type* pointer;- typedef value_type const* const_iterator;- typedef value_type const* const_pointer;- typedef std::ptrdiff_t difference_type;+ using size_type = std::size_t;+ using value_type = Value;+ using allocator_type = std::allocator<Value>;+ using reference = value_type&;+ using const_reference = value_type const&;+ using iterator = value_type*;+ using pointer = value_type*;+ using const_iterator = value_type const*;+ using const_pointer = value_type const*;+ using difference_type = std::ptrdiff_t; - typedef std::reverse_iterator<iterator> reverse_iterator;- typedef std::reverse_iterator<const_iterator> const_reverse_iterator;+ using reverse_iterator = std::reverse_iterator<iterator>;+ using const_reverse_iterator = std::reverse_iterator<const_iterator>; small_vector() = default; // Allocator is unused here. It is taken in for compatibility with std::vector@@ -533,7 +544,7 @@ } auto n = o.size();- makeSize(n);+ makeSize</* IgnoreExistingData */ true>(n); { auto rollback = makeGuard([&] { freeHeap(); }); std::uninitialized_copy(o.begin(), o.begin() + n, begin());@@ -867,12 +878,13 @@ } void shrink_to_fit() {- if (!this->isExtern()) {+ if (!this->isExtern() || size() == capacity()) { return; } - small_vector tmp(begin(), end());- tmp.swap(*this);+ small_vector old = std::exchange(*this, {});+ reserve(old.size());+ std::move(old.begin(), old.end(), std::back_inserter(*this)); } template <class... Args>@@ -1076,7 +1088,7 @@ } void copyWholeInlineStorageTrivial(small_vector const& o) {- static_assert(std::is_trivially_copyable_v<Value>);+ static_assert(detail::small_vector_detail::should_trivially_copy<Value>); FOLLY_PUSH_WARNING FOLLY_GCC_DISABLE_WARNING("-Warray-bounds") std::copy(o.u.buffer(), o.u.buffer() + MaxInline, u.buffer());@@ -1090,9 +1102,15 @@ FOLLY_PUSH_WARNING FOLLY_GCC_DISABLE_WARNING("-Wclass-memaccess") if constexpr (kMayCopyWholeInlineStorage) {- std::memcpy(u.buffer(), o.u.buffer(), MaxInline * kSizeOfValue);+ std::memcpy(+ reinterpret_cast<void*>(u.buffer()),+ reinterpret_cast<const void*>(o.u.buffer()),+ MaxInline * kSizeOfValue); } else {- std::memcpy(u.buffer(), o.u.buffer(), n * kSizeOfValue);+ std::memcpy(+ reinterpret_cast<void*>(u.buffer()),+ reinterpret_cast<const void*>(o.u.buffer()),+ n * kSizeOfValue); } FOLLY_POP_WARNING this->setSize(n);@@ -1155,7 +1173,7 @@ // constructor). template <class It> void constructImpl(It first, It last, std::false_type) {- typedef typename std::iterator_traits<It>::iterator_category categ;+ using categ = typename std::iterator_traits<It>::iterator_category; if (std::is_same<categ, std::input_iterator_tag>::value) { // With iterators that only allow a single pass, we can't really // do anything sane here.@@ -1185,7 +1203,7 @@ template <typename InitFunc> void doConstruct(size_type n, InitFunc&& func) {- makeSize(n);+ makeSize</* IgnoreExistingData */ true>(n); assert(size() == 0); this->incrementSize(n); {@@ -1218,18 +1236,26 @@ return static_cast<size_type>(std::min<size_t>(c, max_size())); } + template <bool IgnoreExistingData = false> void makeSize(size_type newSize) { if (newSize <= capacity()) { return; }- makeSizeInternal(newSize, false, [](void*) { assume_unreachable(); }, 0);+ auto emplaceFunc = [](void*) { assume_unreachable(); };+ makeSizeInternal<+ /* Insert */ false,+ IgnoreExistingData,+ decltype(emplaceFunc)>(newSize, std::move(emplaceFunc), 0); } template <typename EmplaceFunc> void makeSize(size_type newSize, EmplaceFunc&& emplaceFunc, size_type pos) { assert(size() == capacity());- makeSizeInternal(- newSize, true, std::forward<EmplaceFunc>(emplaceFunc), pos);++ makeSizeInternal<+ /* Insert */ true,+ /* IgnoreExistingData */ false,+ EmplaceFunc>(newSize, std::forward<EmplaceFunc>(emplaceFunc), pos); } /*@@ -1242,12 +1268,9 @@ * NOTE: If reallocation is not needed, insert must be false, * because we only know how to emplace elements into new memory. */- template <typename EmplaceFunc>+ template <bool Insert, bool IgnoreExistingData, typename EmplaceFunc> void makeSizeInternal(- size_type newSize,- bool insert,- EmplaceFunc&& emplaceFunc,- size_type pos) {+ size_type newSize, EmplaceFunc&& emplaceFunc, size_type pos) { if (newSize > max_size()) { throw_exception<std::length_error>("max_size exceeded in small_vector"); }@@ -1300,11 +1323,12 @@ auto rollback = makeGuard([&] { // sizedFree(newh, sizeBytes); });- if (insert) {+ if constexpr (Insert) {+ static_assert(!IgnoreExistingData); // move and insert the new element this->moveToUninitializedEmplace( begin(), end(), newp, pos, std::forward<EmplaceFunc>(emplaceFunc));- } else {+ } else if constexpr (!IgnoreExistingData) { // move without inserting new element if (data()) { this->moveToUninitialized(begin(), end(), newp);@@ -1372,13 +1396,13 @@ FOLLY_SV_PACK_POP static constexpr size_t kMaxInlineNonZero = MaxInline ? MaxInline : 1u;- typedef aligned_storage_for_t<value_type[kMaxInlineNonZero]>- InlineStorageDataType;+ using InlineStorageDataType =+ aligned_storage_for_t<value_type[kMaxInlineNonZero]>; - typedef typename std::conditional<+ using InlineStorageType = typename std::conditional< sizeof(value_type) * MaxInline != 0, InlineStorageDataType,- char>::type InlineStorageType;+ char>::type; // If the storage is small enough, it is usually faster to copy it entirely, // instead of just size() values, to make the loop fixed-size and@@ -1388,7 +1412,8 @@ sizeof(InlineStorageType) <= hardware_constructive_interference_size / 2; static constexpr bool kShouldCopyWholeInlineStorageTrivial =- std::is_trivially_copyable_v<Value> && kMayCopyWholeInlineStorage;+ detail::small_vector_detail::should_trivially_copy<Value> &&+ kMayCopyWholeInlineStorage; static bool constexpr kHasInlineCapacity = !BaseType::kAlwaysUseHeap && sizeof(HeapPtrWithCapacity) < sizeof(InlineStorageType);@@ -1422,9 +1447,8 @@ static bool constexpr kAlwaysHasCapacity = kHasInlineCapacity || kMustTrackHeapifiedCapacity; - typedef typename std::- conditional<kHasInlineCapacity, HeapPtrWithCapacity, HeapPtr>::type- PointerType;+ using PointerType = typename std::+ conditional<kHasInlineCapacity, HeapPtrWithCapacity, HeapPtr>::type; bool hasCapacity() const { return kAlwaysHasCapacity || !kHeapifyCapacityThreshold ||
@@ -106,7 +106,7 @@ struct growth_policy_wrapper : private Policy { template <class Container, class Iterator> Iterator increase_capacity(Container& c, Iterator desired_insertion) {- typedef typename Container::difference_type diff_t;+ using diff_t = typename Container::difference_type; diff_t d = desired_insertion - c.begin(); Policy::increase_capacity(c); return c.begin() + d;@@ -186,8 +186,9 @@ ~DirectMutationGuard() noexcept(false) { if (isSortedUnique_) {- assert(detail::is_sorted_unique(- container_.begin(), container_.end(), comp_));+ assert(+ detail::is_sorted_unique(+ container_.begin(), container_.end(), comp_)); return; } as_sorted_unique(container_, comp_);@@ -282,30 +283,30 @@ struct EBO; public:- typedef T value_type;- typedef T key_type;- typedef Compare key_compare;- typedef Compare value_compare;- typedef Allocator allocator_type;- typedef Container container_type;+ using value_type = T;+ using key_type = T;+ using key_compare = Compare;+ using value_compare = Compare;+ using allocator_type = Allocator;+ using container_type = Container; - typedef typename Container::pointer pointer;- typedef typename Container::reference reference;- typedef typename Container::const_reference const_reference;- typedef typename Container::const_pointer const_pointer;+ using pointer = typename Container::pointer;+ using reference = typename Container::reference;+ using const_reference = typename Container::const_reference;+ using const_pointer = typename Container::const_pointer; /* * XXX: Our normal iterator ought to also be a constant iterator * (cf. Defect Report 103 for std::set), but this is a bit more of a * pain. */- typedef typename Container::iterator iterator;- typedef typename Container::const_iterator const_iterator;- typedef typename Container::difference_type difference_type;- typedef typename Container::size_type size_type;- typedef typename Container::reverse_iterator reverse_iterator;- typedef typename Container::const_reverse_iterator const_reverse_iterator;- typedef detail::DirectMutationGuard<Container, value_compare>- direct_mutation_guard;+ using iterator = typename Container::iterator;+ using const_iterator = typename Container::const_iterator;+ using difference_type = typename Container::difference_type;+ using size_type = typename Container::size_type;+ using reverse_iterator = typename Container::reverse_iterator;+ using const_reverse_iterator = typename Container::const_reverse_iterator;+ using direct_mutation_guard =+ detail::DirectMutationGuard<Container, value_compare>; sorted_vector_set() : m_(Compare(), Allocator()) {} @@ -395,8 +396,9 @@ const Compare&, Container&&>::value) : m_(comp, std::move(container)) {- assert(detail::is_sorted_unique(- m_.cont_.begin(), m_.cont_.end(), value_comp()));+ assert(+ detail::is_sorted_unique(+ m_.cont_.begin(), m_.cont_.end(), value_comp())); } Allocator get_allocator() const { return m_.cont_.get_allocator(); }@@ -444,8 +446,9 @@ swap(m_.cont_, newContainer); } void swap_container(sorted_unique_t, Container& newContainer) {- assert(detail::is_sorted_unique(- newContainer.begin(), newContainer.end(), value_comp()));+ assert(+ detail::is_sorted_unique(+ newContainer.begin(), newContainer.end(), value_comp())); using std::swap; swap(m_.cont_, newContainer); }@@ -966,12 +969,12 @@ struct EBO; public:- typedef Key key_type;- typedef Value mapped_type;- typedef typename Container::value_type value_type;- typedef Compare key_compare;- typedef Allocator allocator_type;- typedef Container container_type;+ using key_type = Key;+ using mapped_type = Value;+ using value_type = typename Container::value_type;+ using key_compare = Compare;+ using allocator_type = Allocator;+ using container_type = Container; struct value_compare : private Compare { bool operator()(const value_type& a, const value_type& b) const {@@ -983,18 +986,18 @@ explicit value_compare(const Compare& c) : Compare(c) {} }; - typedef typename Container::pointer pointer;- typedef typename Container::const_pointer const_pointer;- typedef typename Container::reference reference;- typedef typename Container::const_reference const_reference;- typedef typename Container::iterator iterator;- typedef typename Container::const_iterator const_iterator;- typedef typename Container::difference_type difference_type;- typedef typename Container::size_type size_type;- typedef typename Container::reverse_iterator reverse_iterator;- typedef typename Container::const_reverse_iterator const_reverse_iterator;- typedef detail::DirectMutationGuard<Container, value_compare>- direct_mutation_guard;+ using pointer = typename Container::pointer;+ using const_pointer = typename Container::const_pointer;+ using reference = typename Container::reference;+ using const_reference = typename Container::const_reference;+ using iterator = typename Container::iterator;+ using const_iterator = typename Container::const_iterator;+ using difference_type = typename Container::difference_type;+ using size_type = typename Container::size_type;+ using reverse_iterator = typename Container::reverse_iterator;+ using const_reverse_iterator = typename Container::const_reverse_iterator;+ using direct_mutation_guard =+ detail::DirectMutationGuard<Container, value_compare>; sorted_vector_map() noexcept( std::is_nothrow_constructible<EBO, value_compare, Allocator>::value)@@ -1083,8 +1086,9 @@ value_compare, Container&&>::value) : m_(value_compare(comp), std::move(container)) {- assert(detail::is_sorted_unique(- m_.cont_.begin(), m_.cont_.end(), value_comp()));+ assert(+ detail::is_sorted_unique(+ m_.cont_.begin(), m_.cont_.end(), value_comp())); } Allocator get_allocator() const { return m_.cont_.get_allocator(); }@@ -1132,8 +1136,9 @@ swap(m_.cont_, newContainer); } void swap_container(sorted_unique_t, Container& newContainer) {- assert(detail::is_sorted_unique(- newContainer.begin(), newContainer.end(), value_comp()));+ assert(+ detail::is_sorted_unique(+ newContainer.begin(), newContainer.end(), value_comp())); using std::swap; swap(m_.cont_, newContainer); }
@@ -262,7 +262,7 @@ template <typename R> span(R&&) -> span<std::remove_reference_t<- iterator_reference_t<decltype(std::begin(std::declval<R&>()))>>>;+ iterator_reference_t<decltype(std::begin(std::declval<R&>()))>>>; } // namespace fallback_span @@ -284,8 +284,7 @@ struct span_cast_impl_fn { template <- template <typename, std::size_t>- class Span,+ template <typename, std::size_t> class Span, typename U, typename T, std::size_t Extent>
@@ -83,13 +83,13 @@ * Construction * If you know for a fact that all the elements are fitting into SSO buffer, * and you always have complete records (not building) then `tape` does not help- * you, or can even be a slignt regression.+ * you, or can even be a slight regression. * * Otherwise tape can give you good speedups, especially if you need to * `push_back` on individual records. * * Potential future perf improvements.- * * it is possible to do a tape with one allocation for both metada and+ * * it is possible to do a tape with one allocation for both metadata and * data (in special cases). * * when converting indexes to pointers, compiler has to shift. * For contigious containers we can store offsets in bytes.@@ -231,10 +231,9 @@ } template <typename R>- auto push_back(R&& r)- -> std::enable_if_t<- range_of_scalars<R> &&- !std::is_convertible_v<R, const_reference>> // handle \0 separately+ auto push_back(R&& r) -> std::enable_if_t<+ range_of_scalars<R> &&+ !std::is_convertible_v<R, const_reference>> // handle \0 separately { push_back(std::begin(r), std::end(r)); }@@ -265,10 +264,9 @@ } template <typename R>- auto push_back_unsafe(R&& r)- -> std::enable_if_t<- range_of_scalars<R> &&- !std::is_convertible_v<R, const_reference>> // handle \0 separately+ auto push_back_unsafe(R&& r) -> std::enable_if_t<+ range_of_scalars<R> &&+ !std::is_convertible_v<R, const_reference>> // handle \0 separately { push_back_unsafe(std::begin(r), std::end(r)); }@@ -294,10 +292,9 @@ -> std::enable_if_t<iterator_of_scalars<I>, iterator>; template <typename R>- auto insert(const_iterator pos, R&& r)- -> std::enable_if_t<- range_of_scalars<R> && !std::is_convertible_v<R, const_reference>,- iterator> {+ auto insert(const_iterator pos, R&& r) -> std::enable_if_t<+ range_of_scalars<R> && !std::is_convertible_v<R, const_reference>,+ iterator> { return insert(pos, std::begin(r), std::end(r)); }
@@ -162,7 +162,7 @@ inline std::ostream& operator<<(std::ostream& xo, Counts const& counts) { xo << "[";- std::string glue = "";+ std::string glue; if (counts.copyConstruct > 0) { xo << glue << counts.copyConstruct << " copy"; glue = ", ";
@@ -25,8 +25,8 @@ #include <folly/Traits.h> #include <folly/Try.h> #include <folly/coro/AutoCleanup-fwd.h>+#include <folly/coro/BasePromise.h> #include <folly/coro/Coroutine.h>-#include <folly/coro/CurrentExecutor.h> #include <folly/coro/Invoke.h> #include <folly/coro/Result.h> #include <folly/coro/ScopeExit.h>@@ -211,7 +211,7 @@ typename Reference, typename Value = remove_cvref_t<Reference>, bool RequiresCleanup = false>-class FOLLY_NODISCARD AsyncGenerator {+class [[nodiscard]] AsyncGenerator { static_assert( std::is_constructible<Value, Reference>::value, "AsyncGenerator 'value_type' must be constructible from a 'reference'.");@@ -224,6 +224,7 @@ // // Future: Implement a `coro/safe` generator wrapper, like // `async_closure_gen`.+ template <safe_alias> using folly_private_safe_alias_t = safe_alias_constant<safe_alias::unsafe>; private:@@ -252,7 +253,7 @@ class CleanupSemiAwaitable; - class FOLLY_NODISCARD CleanupAwaitable {+ class [[nodiscard]] CleanupAwaitable { public: bool await_ready() noexcept { return !scopeExit_; } @@ -291,12 +292,13 @@ folly::Executor::KeepAlive<> executor_; }; - class FOLLY_NODISCARD CleanupSemiAwaitable {+ class [[nodiscard]] CleanupSemiAwaitable { public: CleanupAwaitable viaIfAsync(Executor::KeepAlive<> executor) noexcept { return CleanupAwaitable{scopeExit_, std::move(executor)}; } + template <safe_alias> using folly_private_safe_alias_t = safe_alias_constant<safe_alias::unsafe>; private:@@ -496,6 +498,7 @@ return NextSemiAwaitable{std::exchange(awaitable.coro_, {})}; } + template <safe_alias> using folly_private_safe_alias_t = safe_alias_constant<safe_alias::unsafe>; private:@@ -554,16 +557,27 @@ coroutine_handle<ScopeExitTaskPromiseBase> scopeExit_; }; -template <typename Reference, typename Value, bool RequiresCleanup = false>+enum class AsyncGeneratorPromiseState : std::uint8_t {+ INVALID,+ VALUE,+ EXCEPTION_WRAPPER,+ DONE,+};++template <+ typename Reference,+ typename Value,+ bool RequiresCleanup /* = false, in BasePromise.h */> class AsyncGeneratorPromise final- : public ExtendedCoroutinePromise,- BaseAsyncGeneratorPromise<RequiresCleanup> {+ : public ExtendedCoroutinePromiseCrtp<+ AsyncGeneratorPromise<Reference, Value, RequiresCleanup>>,+ BaseAsyncGeneratorPromise<RequiresCleanup>,+ public BasePromise<AsyncGeneratorPromiseState> { class YieldAwaiter { public: bool await_ready() noexcept { return false; }- coroutine_handle<> await_suspend(- coroutine_handle<AsyncGeneratorPromise> h) noexcept {- AsyncGeneratorPromise& promise = h.promise();+ coroutine_handle<> await_suspend_promise(+ AsyncGeneratorPromise& promise) noexcept { // Pop AsyncStackFrame first as clearContext() clears the frame state. folly::popAsyncStackFrameCallee(promise.getAsyncFrame()); promise.clearContext();@@ -574,12 +588,21 @@ } return promise.continuation_.getHandle(); }+ coroutine_handle<> await_suspend(+ coroutine_handle<AsyncGeneratorPromise> h) noexcept {+ return await_suspend_promise(h.promise());+ } void await_resume() noexcept {} }; + using State = AsyncGeneratorPromiseState;+ State state() const { return tailStorage_; }+ void setState(State s) { tailStorage_ = s; }+ public: template <typename... Args> AsyncGeneratorPromise(Args&... args) {+ setState(State::INVALID); if constexpr (RequiresCleanup) { scheduleAutoCleanupIfNeeded( coroutine_handle<AsyncGeneratorPromise>::from_promise(*this),@@ -588,7 +611,7 @@ } ~AsyncGeneratorPromise() {- switch (state_) {+ switch (state()) { case State::VALUE: folly::coro::detail::deactivate(value_); break;@@ -624,9 +647,9 @@ YieldAwaiter yield_value(Reference&& value) noexcept( std::is_nothrow_move_constructible<Reference>::value) {- DCHECK(state_ == State::INVALID);+ DCHECK(state() == State::INVALID); folly::coro::detail::activate(value_, static_cast<Reference&&>(value));- state_ = State::VALUE;+ setState(State::VALUE); return YieldAwaiter{}; } @@ -643,17 +666,17 @@ int> = 0> YieldAwaiter yield_value(U&& value) noexcept( std::is_nothrow_constructible_v<Reference, U>) {- DCHECK(state_ == State::INVALID);+ DCHECK(state() == State::INVALID); folly::coro::detail::activate(value_, static_cast<U&&>(value));- state_ = State::VALUE;+ setState(State::VALUE); return {}; } YieldAwaiter yield_value(co_error&& error) noexcept {- DCHECK(state_ == State::INVALID);+ DCHECK(state() == State::INVALID); folly::coro::detail::activate( exceptionWrapper_, std::move(error.exception()));- state_ = State::EXCEPTION_WRAPPER;+ setState(State::EXCEPTION_WRAPPER); return {}; } @@ -687,81 +710,22 @@ return yield_value(co_error(UsingUninitializedTry{})); } + using BasePromise<AsyncGeneratorPromiseState>::await_transform;+ variant_awaitable<YieldAwaiter, ready_awaitable<>> await_transform( co_safe_point_t) noexcept {- if (cancelToken_.isCancellationRequested()) {- return yield_value(co_cancelled);- }- return ready_awaitable<>{};+ return do_safe_point<YieldAwaiter>(*this); } void unhandled_exception() noexcept {- DCHECK(state_ == State::INVALID);+ DCHECK(state() == State::INVALID); folly::coro::detail::activate(exceptionWrapper_, current_exception());- state_ = State::EXCEPTION_WRAPPER;+ setState(State::EXCEPTION_WRAPPER); } void return_void() noexcept {- DCHECK(state_ == State::INVALID);- state_ = State::DONE;- }-- // FIXME: Much of this class is currently copy-pasted from `TaskPromiseBase`,- // Refactor this to use that, so as to avoid `co_await` behavior divergence.-- template <- typename Awaitable,- std::enable_if_t<!must_await_immediately_v<Awaitable>, int> = 0>- auto await_transform(Awaitable&& awaitable) {- bypassExceptionThrowing_ =- bypassExceptionThrowing_ == BypassExceptionThrowing::REQUESTED- ? BypassExceptionThrowing::ACTIVE- : BypassExceptionThrowing::INACTIVE;-- return folly::coro::co_withAsyncStack(folly::coro::co_viaIfAsync(- executor_.get_alias(),- folly::coro::co_withCancellation(- cancelToken_, static_cast<Awaitable&&>(awaitable))));- }- template <- typename Awaitable,- std::enable_if_t<must_await_immediately_v<Awaitable>, int> = 0>- auto await_transform(Awaitable awaitable) {- bypassExceptionThrowing_ =- bypassExceptionThrowing_ == BypassExceptionThrowing::REQUESTED- ? BypassExceptionThrowing::ACTIVE- : BypassExceptionThrowing::INACTIVE;-- return folly::coro::co_withAsyncStack(folly::coro::co_viaIfAsync(- executor_.get_alias(),- folly::coro::co_withCancellation(- cancelToken_,- mustAwaitImmediatelyUnsafeMover(std::move(awaitable))())));- }-- template <typename Awaitable>- auto await_transform(NothrowAwaitable<Awaitable> awaitable) {- bypassExceptionThrowing_ = BypassExceptionThrowing::REQUESTED;- return await_transform(- mustAwaitImmediatelyUnsafeMover(awaitable.unwrap())());- }-- auto await_transform(folly::coro::co_current_executor_t) noexcept {- return ready_awaitable<folly::Executor*>{executor_.get()};- }-- auto await_transform(folly::coro::co_current_cancellation_token_t) noexcept {- return ready_awaitable<const folly::CancellationToken&>{cancelToken_};- }-- void setCancellationToken(folly::CancellationToken cancelToken) noexcept {- // Only keep the first cancellation token.- // ie. the inner-most cancellation scope of the consumer's calling- // context.- if (!hasCancelTokenOverride_) {- cancelToken_ = std::move(cancelToken);- hasCancelTokenOverride_ = true;- }+ DCHECK(state() == State::INVALID);+ setState(State::DONE); } void setExecutor(folly::Executor::KeepAlive<> executor) noexcept {@@ -774,7 +738,7 @@ } bool hasException() const noexcept {- return state_ == State::EXCEPTION_WRAPPER;+ return state() == State::EXCEPTION_WRAPPER; } folly::exception_wrapper& getException() noexcept {@@ -783,7 +747,7 @@ } void throwIfException() {- if (state_ == State::EXCEPTION_WRAPPER) {+ if (state() == State::EXCEPTION_WRAPPER) { exceptionWrapper_.get().throw_exception(); } }@@ -795,33 +759,22 @@ void clearValue() noexcept { if (hasValue()) {- state_ = State::INVALID;+ setState(State::INVALID); folly::coro::detail::deactivate(value_); } else {- CHECK(state_ != State::DONE)+ CHECK(state() != State::DONE) << "Using generator after receiving completion.";- CHECK(state_ != State::EXCEPTION_WRAPPER)+ CHECK(state() != State::EXCEPTION_WRAPPER) << "Using generator after receiving exception."; } } - bool hasValue() const noexcept { return state_ == State::VALUE; }-- folly::AsyncStackFrame& getAsyncFrame() noexcept { return asyncFrame_; }+ bool hasValue() const noexcept { return state() == State::VALUE; } - std::pair<ExtendedCoroutineHandle, AsyncStackFrame*> getErrorHandle(- exception_wrapper& ex) final {- if (bypassExceptionThrowing_ == BypassExceptionThrowing::ACTIVE) {- auto yieldAwaiter = yield_value(co_error(std::move(ex)));- DCHECK(!yieldAwaiter.await_ready());- return {- yieldAwaiter.await_suspend(- coroutine_handle<AsyncGeneratorPromise>::from_promise(*this)),- // yieldAwaiter.await_suspend pops a frame- getAsyncFrame().getParentFrame()};- }- return {- coroutine_handle<AsyncGeneratorPromise>::from_promise(*this), nullptr};+ // Unlike `getErrorHandleUncheckedImpl`, checks the type of `me`.+ static std::optional<ExtendedCoroutineHandle::ErrorHandle> getErrorHandleImpl(+ AsyncGeneratorPromise& me, exception_wrapper& ex) {+ return getErrorHandleUncheckedImpl(me, ex); } private:@@ -845,29 +798,12 @@ return std::exchange(p.scopeExit_, scopeExit); } - enum class State : std::uint8_t {- INVALID,- VALUE,- EXCEPTION_WRAPPER,- DONE,- };-- ExtendedCoroutineHandle continuation_;- folly::AsyncStackFrame asyncFrame_;- folly::Executor::KeepAlive<> executor_;- folly::CancellationToken cancelToken_;+ // From the base: continuation_, asyncFrame_, executor_, cancelToken_,+ // hasCancelTokenOverride_, tailStorage_ == state() / setState() union { ManualLifetime<folly::exception_wrapper> exceptionWrapper_; ManualLifetime<Reference> value_; };- State state_ = State::INVALID;- bool hasCancelTokenOverride_ = false;-- enum class BypassExceptionThrowing : uint8_t {- INACTIVE,- ACTIVE,- REQUESTED,- } bypassExceptionThrowing_{BypassExceptionThrowing::INACTIVE}; }; } // namespace detail
@@ -242,7 +242,7 @@ auto parentToken = co_await co_current_cancellation_token; auto waitResult = co_await co_awaitTry(co_withCancellation(- folly::CancellationToken::merge(+ folly::cancellation_token_merge( std::move(parentToken), cancellationToken_), semaphore_->co_wait())); if (cancellationToken_.isCancellationRequested()) {
@@ -143,8 +143,8 @@ /** * Asynchronously cleanup all started tasks. *- * If you have previuosly called add() then you must call cleanup()- * and wait for the retuned future to complete before the AsyncScope+ * If you have previously called add() then you must call cleanup()+ * and wait for the returned future to complete before the AsyncScope * object destructs. */ SemiFuture<Unit> cleanup() noexcept;@@ -294,10 +294,10 @@ : cancellationToken_(cancellationSource_.getToken()), scope_(throwOnJoin) {} explicit CancellableAsyncScope(CancellationToken&& token)- : cancellationToken_(CancellationToken::merge(+ : cancellationToken_(cancellation_token_merge( cancellationSource_.getToken(), std::move(token))) {} CancellableAsyncScope(CancellationToken&& token, bool throwOnJoin)- : cancellationToken_(CancellationToken::merge(+ : cancellationToken_(cancellation_token_merge( cancellationSource_.getToken(), std::move(token))), scope_(throwOnJoin) {} @@ -326,7 +326,7 @@ void* returnAddress = nullptr) { scope_.add( co_withCancellation(- token ? CancellationToken::merge(*token, cancellationToken_)+ token ? cancellation_token_merge(*token, cancellationToken_) : cancellationToken_, static_cast<Awaitable&&>(awaitable)), returnAddress ? returnAddress : FOLLY_ASYNC_STACK_RETURN_ADDRESS());@@ -340,7 +340,7 @@ source_location sourceLocation = source_location::current()) { scope_.addWithSourceLoc( co_withCancellation(- token ? CancellationToken::merge(*token, cancellationToken_)+ token ? cancellation_token_merge(*token, cancellationToken_) : cancellationToken_, static_cast<Awaitable&&>(awaitable)), returnAddress ? returnAddress : FOLLY_ASYNC_STACK_RETURN_ADDRESS(),@@ -363,12 +363,18 @@ * AsyncScope. The task will be provided a cancellation token to respond to * cancelAndJoinAsync() in the future. *+ * An additional cancellation token may be passed in to apply to the+ * awaitable; it will be merged with the internal token.+ * * Note that cancellation is cooperative, your task must handle cancellation * in order to have any effect. */ template <class T>- folly::coro::Task<void> co_schedule(folly::coro::Task<T>&& task) {- add(co_withExecutor(co_await co_current_executor, std::move(task)));+ folly::coro::Task<void> co_schedule(+ folly::coro::Task<T>&& task,+ std::optional<CancellationToken> token = std::nullopt) {+ add(co_withExecutor(co_await co_current_executor, std::move(task)),+ std::move(token)); } /**
@@ -30,7 +30,7 @@ class AsyncStackTraceAwaitable { class Awaiter { public:- bool await_ready() const { return false; }+ bool await_ready() const noexcept { return false; } template <typename Promise> bool await_suspend(coroutine_handle<Promise> h) noexcept {
@@ -1,239 +0,0 @@-/*- * Copyright (c) Meta Platforms, Inc. and affiliates.- *- * Licensed under the Apache License, Version 2.0 (the "License");- * you may not use this file except in compliance with the License.- * You may obtain a copy of the License at- *- * http://www.apache.org/licenses/LICENSE-2.0- *- * Unless required by applicable law or agreed to in writing, software- * distributed under the License is distributed on an "AS IS" BASIS,- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.- * See the License for the specific language governing permissions and- * limitations under the License.- */--#pragma once--#include <type_traits>--#include <folly/Utility.h>--#if FOLLY_HAS_COROUTINES--namespace folly::coro {--// ## What are immediately-awaitable types, and how should I handle them?-//-// When `must_await_immediately_v<A> == true`, this indicates that the-// awaitable or semiawaitable `A` should be `co_await`ed in the full-expression-// that created it. For an example, see `NowTask.h`. Using immediate-// awaitables reduces the risk of lifetime bugs.-//-// To create a new immediately-awaitable type, follow this protocol:-// - Derive from `public AddMustAwaitImmediately<YourBase>`.-// - Implement `getUnsafeMover(ForMustAwaitImmediately) && noexcept`, but-// first read the ENTIRE docblock of `mustAwaitImmediatelyUnsafeMover()`-// with the notes on object slicing and `noexcept` behavior.-//-// To handle immediately-awaitables, `folly::coro` APIs must follow these rules:-//-// (1) NEVER expose non-static member functions for actions that consume the-// (semi)awaitable. For example, `task.scheduleOn()` was removed in favor-// of `co_withExecutor()`, `.start()` and `.semi()` are unsafe, etc.-//-// INSTEAD: Use static member functions, or ADL CPOs, which take the-// (semi)awaitable by-value.-//-// (2) If your API must also support await-by-reference for types like-// `Baton`, then you must bifurcate the API on the return value of-// `must_await_immediately_v`. Grep for examples.-// - `true`: Pass-by-value-// - `false`: Pass-by-forwarding reference-//-// (3) Immediately-awaitable types are immovable, but you may need to move-// them internally in your library implementation. For this, you can use-// the callable from `mustAwaitImmediatelyUnsafeMover(std::move(t))`. For-// example, you can move the mover into another coroutine, and then invoke-// its `operator()` to reconstitute the (semi)awaitable. Most often, you-// will invoke the mover inline.-//-// DANGER: You should NOT use this to move immovable types outside of-// `folly::coro` library internals, where the lifetime safety is assured-// via pass-by-value from (1) or (2).-//-// Caveat: If you encounter a public `folly::coro` API that is does not yet-// handle immediately-awaitable types, and simply takes the awaitable by `&&`,-// please fix it via one of these paths:-// - If possible, switch it to take all (semi)awaitables by-value.-// - If not, branch the API as in (2) above.-// - Ask for help in the Coroutines group.--namespace detail {--template <typename T>-using must_await_immediately_of_ =- typename T::folly_private_must_await_immediately_t;--template <typename Void, typename T>-struct must_await_immediately_ {- static_assert(- require_sizeof<T>, "`must_await_immediately_t` on incomplete type");- using type = std::false_type;-};--template <>-struct must_await_immediately_<void, void> {- using type = std::false_type;-};--template <typename T>-struct must_await_immediately_<void_t<must_await_immediately_of_<T>>, T> {- // We _could_ do an "is `T` immovable" check, but the cost/benefit seems low.- // That would only guard against a users wrongly adding this to their type:- // folly_private_must_await_immediately_t = std::true_type- // instead of inheriting from `AddMustAwaitImmediately<>`.- using type = must_await_immediately_of_<T>;-};--} // namespace detail--template <typename T>-using must_await_immediately_t =- typename detail::must_await_immediately_<void, T>::type;--template <typename T>-inline constexpr bool must_await_immediately_v =- must_await_immediately_t<T>::value;--// To make a (semi)awaitable immediate, have it publicly inherit from-// `AddMustAwaitImmediately<InnerSemiAwaitable>`. It is templated on the-// "inner type" so that all bases are distinct in a tower-of-wrappers, like-// `TryAwaitable<NowTask<T>>`. Repeating a base would break the empty basea-// optimization (EBO).-template <typename InnerT>-struct AddMustAwaitImmediately : public InnerT {- using folly_private_must_await_immediately_t = std::true_type;-- using InnerT::InnerT;-- // Avoiding `NonCopyableNonMovable` to avoid breaking EBO.- ~AddMustAwaitImmediately() = default;- AddMustAwaitImmediately(AddMustAwaitImmediately&&) = delete;- AddMustAwaitImmediately& operator=(AddMustAwaitImmediately&&) = delete;- AddMustAwaitImmediately(const AddMustAwaitImmediately&) = delete;- AddMustAwaitImmediately& operator=(const AddMustAwaitImmediately&) = delete;-};--// See `mustAwaitImmediatelyUnsafeMover()` for the docs. In short, for an-// `Outer` that is immovable, this stores an unwrapped `Inner` (semi)awaitable-// that can reconstitute `Outer` on `operator()`.-//-// DANGER: Before returning this class from your `getUnsafeMover()`, you must-// review "A note on object slicing" and make sure your usage isn't affected.-template <typename Outer, typename InnerMover>-class MustAwaitImmediatelyUnsafeMover {- private:- InnerMover mover_;-- public:- // `Outer*` is just for type deduction and should be `nullptr`.- MustAwaitImmediatelyUnsafeMover(Outer*, InnerMover m) noexcept- : mover_(std::move(m)) {- // See mustAwaitImmediatelyUnsafeMover docblock- static_assert(std::is_nothrow_move_constructible_v<InnerMover>);- // See "A note on object slicing" below- static_assert(- sizeof(Outer) == sizeof(decltype(FOLLY_DECLVAL(InnerMover)())));- }- Outer operator()() && noexcept {- // See mustAwaitImmediatelyUnsafeMover docblock- static_assert(noexcept(Outer{std::move(mover_)()}));- return Outer{std::move(mover_)()};- }-};--// Analog of `MustAwaitImmediatelyUnsafeMover` for movable (semi)awaitables.-template <typename T>-struct NoOpMover {- private:- T t_;-- public:- explicit NoOpMover(T t) noexcept : t_(std::move(t)) {- // See mustAwaitImmediatelyUnsafeMover docblock- static_assert(std::is_nothrow_move_constructible_v<T>);- }- T operator()() && noexcept { return std::move(t_); }-};--// Overload tag / passkey for the customizable method `getUnsafeMover`.-struct ForMustAwaitImmediately {};--namespace detail {-template <typename T>-using unsafe_mover_for_must_await_immediately_t =- decltype(FOLLY_DECLVAL(T).getUnsafeMover(ForMustAwaitImmediately{}));-}--// After taking an immediately-(semi)awaitable by-value, this lets-// `folly::coro` libraries move it internally. They MUST make sure not to let-// the awaitable be used outside of its original full-expression.-//-// This wraps `getUnsafeMover` for types that implement it, and provides-// a no-op fallback for those that don't. Required semantics:-//-// - It's a destructive operation -- hence the r-value qualifier-// - It takes ownership of the internals of `awaitable`.-// - It returns a mover value (never a reference), whose `operator() &&` is a-// single-use operation that returns a new awaitable equivalent to the-// original `awaitable` that was passed in & moved out.-//-// ## A note on object slicing -- for `getUnsafeMover` implementations-//-// The default `getUnsafeMover()` implementations return `NoOpMover` or-// `MustAwaitImmediatelyUnsafeMover`. The net effect is that the mover-// stores an unwrapped, inner type that is movable (like `Task`), and-// its `operator()` reconstitues the original "outer" type.-//-// This is fine for type-only wrappers. But, object slicing is a danger for-// wrappers that affect the object's lifetime management or layout. For example:-// - If "outer" adds a new member, this would be discarded. The current-// `getUnsafeMover`s compare before/after `sizeof`. Do that in new ones!-// - If the wrapper customizes destruction, move, copy, or assignment, then-// the wrapping/unwrapping will cause the custom logic will run at an-// unexpected time. Such wrapper types MUST customize `getUnsafeMover` to-// return a custom mover that handles this correctly.-//-// ## A note on `noexcept` discipline-//-// This `static_assert`s that constructing **and** using a mover is `noexcept`.-// This could, of course, be relaxed via `noexcept(noexcept(...))` logic, but-// IMO no `folly::coro` awaitables SHOULD have throwing move ctors, so this-// requirement is Actually Fine (until proven otherwise).-template <- typename Awaitable,- typename DetectRes = detected_or<- NoOpMover<Awaitable>,- detail::unsafe_mover_for_must_await_immediately_t,- Awaitable>>-// CAREFUL: Passing by `&&` can violate the immediately-awaitable restriction!-typename DetectRes::type mustAwaitImmediatelyUnsafeMover(- Awaitable&& awaitable) noexcept {- static_assert(noexcept(FOLLY_DECLVAL(typename DetectRes::type&&)()));- if constexpr (DetectRes::value_t::value) {- static_assert(noexcept(static_cast<Awaitable&&>(awaitable).getUnsafeMover(- ForMustAwaitImmediately{})));- return static_cast<Awaitable&&>(awaitable).getUnsafeMover(- ForMustAwaitImmediately{});- } else {- static_assert(- std::is_nothrow_constructible_v<NoOpMover<Awaitable>, Awaitable&&>);- return NoOpMover<Awaitable>{static_cast<Awaitable&&>(awaitable)};- }-}--} // namespace folly::coro--#endif
@@ -1,158 +0,0 @@-/*- * Copyright (c) Meta Platforms, Inc. and affiliates.- *- * Licensed under the Apache License, Version 2.0 (the "License");- * you may not use this file except in compliance with the License.- * You may obtain a copy of the License at- *- * http://www.apache.org/licenses/LICENSE-2.0- *- * Unless required by applicable law or agreed to in writing, software- * distributed under the License is distributed on an "AS IS" BASIS,- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.- * See the License for the specific language governing permissions and- * limitations under the License.- */--#pragma once--#include <folly/coro/ViaIfAsync.h>-#include <folly/result/try.h>--#if FOLLY_HAS_RESULT--namespace folly::coro {--/// `co_await_result` is the `result<T>` analog of the older `co_awaitTry`.-///-/// In a `folly::coro` async coroutine, use `co_await_result` like so:-///-/// result<int> res = co_await co_await_result(taskReturningInt());-/// if (auto* ex = get_exception<MyError>(res)) {-/// /* handle ex */-/// } else {-/// sum += co_await co_ready(res); // efficiently propagate unhandled error-/// }-///-/// Contrast that with related async coro vocabulary:-/// - `co_yield co_result(r)` from `Result.h` -- propagate `result<T>` or-/// `Try<T>` to the awaiter of the current coro.-/// - `auto& v = co_await co_ready(r)` from `Ready.h` -- given a `result<T>`,-/// unpack the value, or propagate any error to our awaiter.-///-/// The purpose of `co_await_result` is to handle errors from a child task via-/// `result<T>`, rather than through `try {} catch {}`. Some reasons to do so:-/// - Your error-handling APIs (logging, retry, etc) use `result<T>`.-/// - You wish to avoid the ~microsecond cost of thrown exceptions,-/// applicable only when your error path is hot, and the child uses-/// `co_yield` instead of `throw` to propagate exceptions.--namespace detail {--template <typename Awaiter>-using detect_await_resume_result =- decltype(FOLLY_DECLVAL(Awaiter).await_resume_result());--template <typename Awaiter>-constexpr bool is_awaiter_result =- is_detected_v<detect_await_resume_result, Awaiter>;--template <typename Awaitable>-constexpr bool is_awaitable_result =- is_awaiter_result<awaiter_type_t<Awaitable>>;--// On the happy path, this uses the dedicated `await_resume_result()` protocol,-// if it's supported by the awaiter.-//-// As fallback, this reuses the `await_resume_try()` machinery in the hope that-// the compiler will be able to optimize away the `Try` -> `result` conversion.-//-// The reasons to support the dedicated protocol are (1) better semantics, and-// (2) a data flow that's easier for the compiler to optimize. Specifically:-//-// - `await_resume_result()` cleanly handles `Task<V&>`, whereas `Try`-// doesn't support storing references, and the caller of `co_awaitTry` has-// to deal with `Try<std::reference_wrapper<V>>`. See the test in-// `TaskOfLvalueReferenceAsTry`.-//-// - `await_resume_result()` implementations can explicitly avoid the "empty-// `Try`" pitfall, which is something that gets converted to a-// `UsingUninitializedTry` error by the `try_to_result()` fallback.-//-// - Falling back to `await_resume_try()` can incur an extra move-copy, which-// may not always optimize away.-template <typename Awaitable>-class ResultAwaiter {- private:- static_assert(is_awaitable_try<Awaitable> || is_awaitable_result<Awaitable>);-- using Awaiter = awaiter_type_t<Awaitable>;- Awaiter awaiter_;-- public:- explicit ResultAwaiter(Awaitable&& awaiter)- : awaiter_(get_awaiter(static_cast<Awaitable&&>(awaiter))) {}-- // clang-format off- auto await_ready() FOLLY_DETAIL_FORWARD_BODY(awaiter_.await_ready())-- template <typename Promise>- auto await_suspend(coroutine_handle<Promise> coro)- FOLLY_DETAIL_FORWARD_BODY(awaiter_.await_suspend(coro))- // clang-format on-- template <- typename Awaiter2 = Awaiter,- typename Result =- decltype(FOLLY_DECLVAL(Awaiter2&).await_resume_result())>- Result await_resume() noexcept(noexcept(awaiter_.await_resume_result())) {- return awaiter_.await_resume_result();- }-- template <- typename Awaiter2 = Awaiter,- typename Result =- decltype(try_to_result(FOLLY_DECLVAL(Awaiter2&).await_resume_try()))>- Result await_resume() noexcept(- noexcept(try_to_result(awaiter_.await_resume_try())))- requires(!is_awaitable_result<Awaitable>)- {- return try_to_result(awaiter_.await_resume_try());- }-};--template <typename T>-class [[FOLLY_ATTR_CLANG_CORO_AWAIT_ELIDABLE]] ResultAwaitable- : public CommutativeWrapperAwaitable<ResultAwaitable, T> {- public:- using CommutativeWrapperAwaitable<ResultAwaitable, T>::- CommutativeWrapperAwaitable;-- template <- typename Self,- std::enable_if_t<- std::is_same_v<remove_cvref_t<Self>, ResultAwaitable>,- int> = 0,- typename T2 = like_t<Self, T>,- std::enable_if_t<is_awaitable_v<T2>, int> = 0>- friend ResultAwaiter<T2> operator co_await(Self && self) {- return ResultAwaiter<T2>{static_cast<Self&&>(self).inner_};- }-- using folly_private_noexcept_awaitable_t = std::true_type;-};--} // namespace detail--// IMPORTANT: If you need an `Awaitable&&` overload, you must bifurcate this-// API on `must_await_immediately_v`, see `co_awaitTry` for an example.-template <typename Awaitable>-detail::ResultAwaitable<Awaitable> co_await_result(- [[FOLLY_ATTR_CLANG_CORO_AWAIT_ELIDABLE_ARGUMENT]] Awaitable awaitable) {- return detail::ResultAwaitable<Awaitable>{- mustAwaitImmediatelyUnsafeMover(std::move(awaitable))()};-}--} // namespace folly::coro--#endif
@@ -0,0 +1,147 @@+/*+ * Copyright (c) Meta Platforms, Inc. and affiliates.+ *+ * Licensed under the Apache License, Version 2.0 (the "License");+ * you may not use this file except in compliance with the License.+ * You may obtain a copy of the License at+ *+ * http://www.apache.org/licenses/LICENSE-2.0+ *+ * Unless required by applicable law or agreed to in writing, software+ * distributed under the License is distributed on an "AS IS" BASIS,+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.+ * See the License for the specific language governing permissions and+ * limitations under the License.+ */++#pragma once++#include <folly/Portability.h> // FOLLY_HAS_COROUTINES+#include <folly/coro/CurrentExecutor.h>+#include <folly/coro/Error.h>+#include <folly/coro/Nothrow.h>+#include <folly/coro/ValueOrError.h>++/// Coroutine promise implementation shared between tasks and async generators.+/// Only the `await_transform` signatures here are of interest to end-users.++#if FOLLY_HAS_COROUTINES++namespace folly::coro::detail {++class TaskPromiseBase;++template <typename Reference, typename Value, bool RequiresCleanup = false>+class AsyncGeneratorPromise;++template <typename TailStorage = Unit>+class BasePromise {+ protected:+ // The destructor is protected to alert future authors -- this is a TIGHTLY+ // COUPLED DETAIL of task & async generator, not an easily reusable+ // component. For example, it relies on correctly specified+ // `await_transform` behavior, a correct definition of the `getErrorHandle`+ // protocol, and its correct usage in `await_suspend`.+ friend class TaskPromiseBase;+ template <typename, typename, bool>+ friend class AsyncGeneratorPromise;+ ~BasePromise() = default;++ ExtendedCoroutineHandle continuation_;+ folly::AsyncStackFrame asyncFrame_;+ folly::Executor::KeepAlive<> executor_;+ folly::CancellationToken cancelToken_;+ bool hasCancelTokenOverride_ = false;+ BypassExceptionThrowing bypassThrowing_;+ // Let derived classes pack data in one word with the prior 2 members+ // This could perhaps be avoided by using bitfields...+ TailStorage tailStorage_;++ // Implementation of `await_transform(co_safe_point_t)`+ template <typename Awaiter, typename Promise>+ variant_awaitable<Awaiter, ready_awaitable<>> do_safe_point(+ Promise& promise) noexcept {+ if (cancelToken_.isCancellationRequested()) {+ return promise.yield_value(co_stopped_may_throw);+ }+ return ready_awaitable<>{};+ }++ // Async generator & task specialize this to check the type of Promise+ template <typename Promise>+ static std::optional<ExtendedCoroutineHandle::ErrorHandle>+ getErrorHandleUncheckedImpl(Promise& me, exception_wrapper& ex) {+ if (me.bypassThrowing_.shouldBypassFor(ex)) {+ auto finalAwaiter = me.yield_value(co_error(std::move(ex)));+ DCHECK(!finalAwaiter.await_ready());+ return ExtendedCoroutineHandle::ErrorHandle{+ finalAwaiter.await_suspend_promise(me),+ // finalAwaiter.await_suspend pops a frame+ me.getAsyncFrame().getParentFrame()};+ }+ return std::nullopt;+ }++ public:+ template <+ typename Awaitable,+ std::enable_if_t<!folly::ext::must_use_immediately_v<Awaitable>, int> = 0>+ auto await_transform(Awaitable&& awaitable) {+ bypassThrowing_.maybeActivate<Awaitable>();+ return folly::coro::co_withAsyncStack(+ folly::coro::co_viaIfAsync(+ executor_.get_alias(),+ folly::coro::co_withCancellation(+ cancelToken_, static_cast<Awaitable&&>(awaitable))));+ }+ template <+ typename Awaitable,+ std::enable_if_t<folly::ext::must_use_immediately_v<Awaitable>, int> = 0>+ auto await_transform(Awaitable awaitable) {+ bypassThrowing_.maybeActivate<Awaitable>();+ return folly::coro::co_withAsyncStack(+ folly::coro::co_viaIfAsync(+ executor_.get_alias(),+ folly::coro::co_withCancellation(+ cancelToken_,+ folly::ext::must_use_immediately_unsafe_mover(+ std::move(awaitable))())));+ }++ template <typename Awaitable>+ auto await_transform(NothrowAwaitable<Awaitable> awaitable) {+ bypassThrowing_.requestDueToNothrow<Awaitable>();+ return await_transform(+ folly::ext::must_use_immediately_unsafe_mover(awaitable.unwrap())());+ }++ template <typename Awaitable>+ auto await_transform(ValueOrError<Awaitable> awaitable) {+ bypassThrowing_.template requestDueToValueOrError<Awaitable>();+ return await_transform(std::move(awaitable).toValueOrErrorImpl());+ }++ auto await_transform(co_current_executor_t) noexcept {+ return ready_awaitable<folly::Executor*>{executor_.get()};+ }++ auto await_transform(co_current_cancellation_token_t) noexcept {+ return ready_awaitable<const folly::CancellationToken&>{cancelToken_};+ }++ // End-users can do this via `co_withCancellation`.+ void setCancellationToken(folly::CancellationToken cancelToken) noexcept {+ // Only keep the first cancellation token. This is usually the inner-most+ // cancellation scope of the consumer's calling context.+ if (!hasCancelTokenOverride_) {+ cancelToken_ = std::move(cancelToken);+ hasCancelTokenOverride_ = true;+ }+ }++ folly::AsyncStackFrame& getAsyncFrame() noexcept { return asyncFrame_; }+};++} // namespace folly::coro::detail++#endif // FOLLY_HAS_COROUTINES
@@ -17,7 +17,6 @@ #pragma once #include <folly/Try.h>-#include <folly/coro/AwaitImmediately.h> #include <folly/coro/Coroutine.h> #include <folly/coro/Task.h> #include <folly/coro/Traits.h>@@ -26,11 +25,14 @@ #include <folly/coro/detail/Malloc.h> #include <folly/coro/detail/Traits.h> #include <folly/executors/ManualExecutor.h>+#include <folly/executors/SequencedExecutor.h> #include <folly/fibers/Baton.h>+#include <folly/lang/MustUseImmediately.h> #include <folly/synchronization/Baton.h> #include <folly/tracing/AsyncStack.h> #include <cassert>+#include <deque> #include <exception> #include <type_traits> #include <utility>@@ -271,7 +273,9 @@ co_yield co_await static_cast<Awaitable&&>(awaitable); } -class BlockingWaitExecutor final : public folly::DrivableExecutor {+class BlockingWaitExecutor final+ : public folly::DrivableExecutor,+ public SequencedExecutor { public: ~BlockingWaitExecutor() override { while (keepAliveCount_.load() > 0) {@@ -284,7 +288,8 @@ { auto wQueue = queue_.wlock(); empty = wQueue->empty();- wQueue->push_back(std::move(func));+ wQueue->emplace_back(+ std::move(func), folly::RequestContext::saveContext()); } if (empty) { baton_.post();@@ -296,10 +301,12 @@ baton_.reset(); folly::fibers::runInMainContext([&]() {- std::vector<Func> funcs;- queue_.swap(funcs);- for (auto& func : funcs) {- std::exchange(func, nullptr)();+ std::deque<BlockingWaitTaskInfo> infos;+ queue_.swap(infos);+ RequestContextSaverScopeGuard guard;+ for (auto& info : infos) {+ folly::RequestContext::setContext(std::move(info.rctx));+ std::exchange(info.func, nullptr)(); } }); }@@ -331,7 +338,14 @@ std::memory_order_relaxed)); } - folly::Synchronized<std::vector<Func>> queue_;+ struct BlockingWaitTaskInfo {+ Func func;+ std::shared_ptr<folly::RequestContext> rctx;+ BlockingWaitTaskInfo(Func f, std::shared_ptr<folly::RequestContext> r)+ : func(std::move(f)), rctx(std::move(r)) {}+ };++ folly::Synchronized<std::deque<BlockingWaitTaskInfo>> queue_; fibers::Baton baton_; std::atomic<ssize_t> keepAliveCount_{0};@@ -371,7 +385,9 @@ template < typename SemiAwaitable,- std::enable_if_t<!must_await_immediately_v<SemiAwaitable>, int> = 0>+ std::enable_if_t<+ !folly::ext::must_use_immediately_v<SemiAwaitable>,+ int> = 0> FOLLY_NOINLINE auto operator()( SemiAwaitable&& awaitable, folly::DrivableExecutor* executor) const -> detail::decay_rvalue_reference_t<semi_await_result_t<SemiAwaitable>> {@@ -393,7 +409,8 @@ } template < typename SemiAwaitable,- std::enable_if_t<must_await_immediately_v<SemiAwaitable>, int> = 0>+ std::enable_if_t<folly::ext::must_use_immediately_v<SemiAwaitable>, int> =+ 0> FOLLY_NOINLINE auto operator()( SemiAwaitable awaitable, folly::DrivableExecutor* executor) const -> detail::decay_rvalue_reference_t<semi_await_result_t<SemiAwaitable>> {@@ -410,14 +427,17 @@ detail::makeRefBlockingWaitTask( folly::coro::co_viaIfAsync( folly::getKeepAliveToken(executor),- mustAwaitImmediatelyUnsafeMover(std::move(awaitable))()))+ folly::ext::must_use_immediately_unsafe_mover(+ std::move(awaitable))())) .getVia(executor, frame)); } template < typename SemiAwaitable, std::enable_if_t<!is_awaitable_v<SemiAwaitable>, int> = 0,- std::enable_if_t<!must_await_immediately_v<SemiAwaitable>, int> = 0>+ std::enable_if_t<+ !folly::ext::must_use_immediately_v<SemiAwaitable>,+ int> = 0> auto operator()(SemiAwaitable&& awaitable) const -> detail::decay_rvalue_reference_t<semi_await_result_t<SemiAwaitable>> { std::exception_ptr eptr;@@ -434,7 +454,8 @@ template < typename SemiAwaitable, std::enable_if_t<!is_awaitable_v<SemiAwaitable>, int> = 0,- std::enable_if_t<must_await_immediately_v<SemiAwaitable>, int> = 0>+ std::enable_if_t<folly::ext::must_use_immediately_v<SemiAwaitable>, int> =+ 0> auto operator()(SemiAwaitable awaitable) const -> detail::decay_rvalue_reference_t<semi_await_result_t<SemiAwaitable>> { std::exception_ptr eptr;@@ -442,7 +463,9 @@ detail::BlockingWaitExecutor executor; try { return operator()(- mustAwaitImmediatelyUnsafeMover(std::move(awaitable))(), &executor);+ folly::ext::must_use_immediately_unsafe_mover(+ std::move(awaitable))(),+ &executor); } catch (...) { eptr = current_exception(); }
@@ -84,10 +84,11 @@ cancelToken, static_cast<SemiAwaitableMover&&>(mover)())); result.emplace(); } else {- result.emplace(co_await co_viaIfAsync(- std::move(executor),- co_withCancellation(- cancelToken, static_cast<SemiAwaitableMover&&>(mover)())));+ result.emplace(+ co_await co_viaIfAsync(+ std::move(executor),+ co_withCancellation(+ cancelToken, static_cast<SemiAwaitableMover&&>(mover)()))); } } catch (...) { result.emplaceException(current_exception());@@ -158,7 +159,7 @@ Ret collectAllImpl( tag_t<Ret, SemiAwaitables...>, std::index_sequence<Indices...>,- // `semiFns()` is the immovable, must-await-immediately `SemiAwaitable`+ // `semiFns()` is the immovable, must-use-immediately `SemiAwaitable` SemiFns... semiFns) { if constexpr (sizeof...(SemiAwaitables) == 0) { co_return std::tuple<>{};@@ -169,7 +170,7 @@ const CancellationSource cancelSource; const CancellationToken cancelToken =- CancellationToken::merge(parentCancelToken, cancelSource.getToken());+ cancellation_token_merge(parentCancelToken, cancelSource.getToken()); exception_wrapper firstException; @@ -184,10 +185,11 @@ cancelToken, static_cast<decltype(fn)>(fn)())); result.emplace(); } else {- result.emplace(co_await co_viaIfAsync(- executor.get_alias(),- co_withCancellation(- cancelToken, static_cast<decltype(fn)>(fn)())));+ result.emplace(+ co_await co_viaIfAsync(+ executor.get_alias(),+ co_withCancellation(+ cancelToken, static_cast<decltype(fn)>(fn)()))); } } catch (...) { if (!cancelSource.requestCancellation()) {@@ -324,24 +326,25 @@ co_await co_current_cancellation_token; const CancellationSource cancelSource; const CancellationToken cancelToken =- CancellationToken::merge(parentCancelToken, cancelSource.getToken());+ cancellation_token_merge(parentCancelToken, cancelSource.getToken()); std::pair<std::size_t, folly::Try<collect_any_component_t<SemiAwaitables...>>> firstCompletion; firstCompletion.first = size_t(-1);- co_await folly::coro::collectAll(folly::coro::co_withCancellation(- cancelToken,- folly::coro::co_invoke(- [&, aw = static_cast<SemiAwaitables&&>(awaitables)]() mutable- -> folly::coro::Task<void> {- auto result = co_await folly::coro::co_awaitTry(- static_cast<SemiAwaitables&&>(aw));- if (!cancelSource.requestCancellation()) {- // This is first entity to request cancellation.- firstCompletion.first = Indices;- firstCompletion.second = std::move(result);- }- }))...);+ co_await folly::coro::collectAll(+ folly::coro::co_withCancellation(+ cancelToken,+ folly::coro::co_invoke(+ [&, aw = static_cast<SemiAwaitables&&>(awaitables)]() mutable+ -> folly::coro::Task<void> {+ auto result = co_await folly::coro::co_awaitTry(+ static_cast<SemiAwaitables&&>(aw));+ if (!cancelSource.requestCancellation()) {+ // This is first entity to request cancellation.+ firstCompletion.first = Indices;+ firstCompletion.second = std::move(result);+ }+ }))...); co_return firstCompletion; }@@ -356,24 +359,26 @@ co_await co_current_cancellation_token; const CancellationSource cancelSource; const CancellationToken cancelToken =- CancellationToken::merge(parentCancelToken, cancelSource.getToken());+ cancellation_token_merge(parentCancelToken, cancelSource.getToken()); constexpr std::size_t nAwaitables = sizeof...(SemiAwaitables); std::atomic<std::size_t> nAwaited = 1; std::pair<std::size_t, folly::Try<collect_any_component_t<SemiAwaitables...>>> firstValueOrLastException; firstValueOrLastException.first = std::numeric_limits<size_t>::max();- co_await folly::coro::collectAll(folly::coro::co_withCancellation(- cancelToken, [&]() -> folly::coro::Task<void> {- auto result = co_await folly::coro::co_awaitTry(- std::forward<SemiAwaitables>(awaitables));- if ((result.hasValue() ||- nAwaited.fetch_add(1, std::memory_order_relaxed) == nAwaitables) &&- !cancelSource.requestCancellation()) {- firstValueOrLastException.first = Indices;- firstValueOrLastException.second = std::move(result);- }- }())...);+ co_await folly::coro::collectAll(+ folly::coro::co_withCancellation(+ cancelToken, [&]() -> folly::coro::Task<void> {+ auto result = co_await folly::coro::co_awaitTry(+ std::forward<SemiAwaitables>(awaitables));+ if ((result.hasValue() ||+ nAwaited.fetch_add(1, std::memory_order_relaxed) ==+ nAwaitables) &&+ !cancelSource.requestCancellation()) {+ firstValueOrLastException.first = Indices;+ firstValueOrLastException.second = std::move(result);+ }+ }())...); co_return firstValueOrLastException; }@@ -384,17 +389,18 @@ -> folly::coro::Task< std::tuple<collect_all_try_component_t<SemiAwaitables>...>> { const CancellationSource cancelSource;- const CancellationToken cancelToken = CancellationToken::merge(+ const CancellationToken cancelToken = cancellation_token_merge( co_await co_current_cancellation_token, cancelSource.getToken()); std::tuple<collect_all_try_component_t<SemiAwaitables>...> results;- co_await folly::coro::collectAll(folly::coro::co_withCancellation(- cancelToken, folly::coro::co_invoke([&]() -> folly::coro::Task<void> {- auto result = co_await folly::coro::co_awaitTry(- std::forward<SemiAwaitables>(awaitables));- cancelSource.requestCancellation();- std::get<Indices>(results) = std::move(result);- }))...);+ co_await folly::coro::collectAll(+ folly::coro::co_withCancellation(+ cancelToken, folly::coro::co_invoke([&]() -> folly::coro::Task<void> {+ auto result = co_await folly::coro::co_awaitTry(+ std::forward<SemiAwaitables>(awaitables));+ cancelSource.requestCancellation();+ std::get<Indices>(results) = std::move(result);+ }))...); co_return results; }@@ -407,7 +413,7 @@ return detail::collectAllImpl( tag<detail::CollectAllTask<SemiAwaitables...>, SemiAwaitables...>, std::make_index_sequence<sizeof...(SemiAwaitables)>{},- mustAwaitImmediatelyUnsafeMover(+ folly::ext::must_use_immediately_unsafe_mover( static_cast<SemiAwaitables&&>(awaitables))...); } @@ -417,7 +423,7 @@ return detail::collectAllTryImpl( tag<detail::CollectAllTryTask<SemiAwaitables...>, SemiAwaitables...>, std::make_index_sequence<sizeof...(SemiAwaitables)>{},- mustAwaitImmediatelyUnsafeMover(+ folly::ext::must_use_immediately_unsafe_mover( static_cast<SemiAwaitables&&>(awaitables))...); } @@ -432,7 +438,7 @@ detail::range_reference_t<InputRange>>>> { const folly::Executor::KeepAlive<> executor = co_await co_current_executor; const CancellationSource cancelSource;- const CancellationToken cancelToken = CancellationToken::merge(+ const CancellationToken cancelToken = cancellation_token_merge( co_await co_current_cancellation_token, cancelSource.getToken()); std::vector<detail::collect_all_try_range_component_t<@@ -447,9 +453,10 @@ assert(index < tryResults.size()); try {- tryResults[index].emplace(co_await co_viaIfAsync(- executor.get_alias(),- co_withCancellation(cancelToken, std::move(semiAwaitable))));+ tryResults[index].emplace(+ co_await co_viaIfAsync(+ executor.get_alias(),+ co_withCancellation(cancelToken, std::move(semiAwaitable)))); } catch (...) { if (!cancelSource.requestCancellation()) { firstException = exception_wrapper{current_exception()};@@ -504,7 +511,7 @@ auto collectAllRange(InputRange awaitables) -> folly::coro::Task<void> { const folly::Executor::KeepAlive<> executor = co_await co_current_executor; const CancellationSource cancelSource;- const CancellationToken cancelToken = CancellationToken::merge(+ const CancellationToken cancelToken = cancellation_token_merge( co_await co_current_cancellation_token, cancelSource.getToken()); exception_wrapper firstException;@@ -575,9 +582,10 @@ co_withCancellation(cancelToken, std::move(semiAwaitable))); result.emplace(); } else {- result.emplace(co_await co_viaIfAsync(- executor.get_alias(),- co_withCancellation(cancelToken, std::move(semiAwaitable))));+ result.emplace(+ co_await co_viaIfAsync(+ executor.get_alias(),+ co_withCancellation(cancelToken, std::move(semiAwaitable)))); } } catch (...) { result.emplaceException(current_exception());@@ -624,7 +632,7 @@ const folly::Executor::KeepAlive<> executor = co_await co_current_executor; const CancellationSource cancelSource;- const CancellationToken cancelToken = CancellationToken::merge(+ const CancellationToken cancelToken = cancellation_token_merge( co_await co_current_cancellation_token, cancelSource.getToken()); exception_wrapper firstException;@@ -741,7 +749,7 @@ co_await co_current_cancellation_token; const CancellationSource cancelSource; const CancellationToken cancelToken =- CancellationToken::merge(parentCancelToken, cancelSource.getToken());+ cancellation_token_merge(parentCancelToken, cancelSource.getToken()); exception_wrapper firstException; @@ -764,6 +772,13 @@ detail::range_reference_t<InputRange>>> tryResults; + if constexpr (is_invocable_v<folly::access::size_fn, InputRange&>) {+ tryResults.reserve(+ static_cast<std::size_t>(folly::access::size(awaitables)));+ } else if constexpr (range_has_known_distance_v<InputRange&>) {+ tryResults.reserve(static_cast<std::size_t>(std::distance(iter, iterEnd)));+ }+ exception_wrapper iterationException; auto makeWorker = [&]() -> detail::BarrierTask {@@ -793,10 +808,11 @@ tryResult; try {- tryResult.emplace(co_await co_viaIfAsync(- executor.get_alias(),- co_withCancellation(- cancelToken, static_cast<awaitable_t&&>(*awaitable))));+ tryResult.emplace(+ co_await co_viaIfAsync(+ executor.get_alias(),+ co_withCancellation(+ cancelToken, static_cast<awaitable_t&&>(*awaitable)))); } catch (...) { trySetFirstException(exception_wrapper{current_exception()}); }@@ -930,9 +946,10 @@ co_withCancellation(cancelToken, std::move(*awaitable))); result.emplace(); } else {- result.emplace(co_await co_viaIfAsync(- executor.get_alias(),- co_withCancellation(cancelToken, std::move(*awaitable))));+ result.emplace(+ co_await co_viaIfAsync(+ executor.get_alias(),+ co_withCancellation(cancelToken, std::move(*awaitable)))); } } catch (...) { result.emplaceException(current_exception());@@ -1069,16 +1086,15 @@ } template <typename InputRange>-auto collectAnyRange(InputRange awaitables)- -> folly::coro::Task<std::pair<- size_t,- folly::Try<detail::collect_all_range_component_t<- detail::range_reference_t<InputRange>>>>> {+auto collectAnyRange(InputRange awaitables) -> folly::coro::Task<std::pair<+ size_t,+ folly::Try<detail::collect_all_range_component_t<+ detail::range_reference_t<InputRange>>>>> { const CancellationToken& parentCancelToken = co_await co_current_cancellation_token; const CancellationSource cancelSource; const CancellationToken cancelToken =- CancellationToken::merge(parentCancelToken, cancelSource.getToken());+ cancellation_token_merge(parentCancelToken, cancelSource.getToken()); std::pair< size_t,@@ -1116,7 +1132,7 @@ co_await co_current_cancellation_token; const CancellationSource cancelSource; const CancellationToken cancelToken =- CancellationToken::merge(parentCancelToken, cancelSource.getToken());+ cancellation_token_merge(parentCancelToken, cancelSource.getToken()); size_t nAwaitables; std::atomic<std::size_t> nAwaited = 1;@@ -1155,7 +1171,7 @@ co_await co_current_cancellation_token; const CancellationSource cancelSource; const CancellationToken cancelToken =- CancellationToken::merge(parentCancelToken, cancelSource.getToken());+ cancellation_token_merge(parentCancelToken, cancelSource.getToken()); std::vector<detail::collect_all_try_range_component_t< detail::range_reference_t<InputRange>>>
@@ -27,9 +27,9 @@ #include <folly/coro/ViaIfAsync.h> #include <folly/coro/detail/PickTaskWrapper.h> #include <folly/coro/detail/Traits.h>-// `collectAll(coroFutureInt())` makes a `SafeTask`+// `collectAll(coroFutureInt())` makes a `safe_task` #include <folly/coro/safe/SafeTask.h>-// `collectAll(memberTask())` makes a `NowTask`+// `collectAll(memberTask())` makes a `now_task` #include <folly/coro/safe/NowTask.h> #include <functional>@@ -85,20 +85,22 @@ Container& container_; }; -// Future: Apply `AsNoexcept` to the task if the entire collection process is-// noexcept-awaitable. This would require reworking the implementation a bit,-// since e.g. `CancellationToken::merge` can throw `bad_alloc`.+// Future: Apply `value_or_fatal` to the task if the entire collection process+// is value-only awaitable. This would require reworking the implementation a+// bit, since e.g. `cancellation_token_merge` can throw `bad_alloc`. template <typename... SemiAwaitables>-using CollectAllTask = PickTaskWrapper<+using CollectAllTask = pick_task_wrapper< std::tuple<collect_all_component_t<remove_cvref_t<SemiAwaitables>>...>,- std::min({safe_alias::maybe_value, safe_alias_of_v<SemiAwaitables>...}),- (must_await_immediately_v<SemiAwaitables> || ...)>;+ std::min(+ {safe_alias::maybe_value, lenient_safe_alias_of_v<SemiAwaitables>...}),+ (folly::ext::must_use_immediately_v<SemiAwaitables> || ...)>; template <typename... SemiAwaitables>-using CollectAllTryTask = PickTaskWrapper<+using CollectAllTryTask = pick_task_wrapper< std::tuple<collect_all_try_component_t<remove_cvref_t<SemiAwaitables>>...>,- std::min({safe_alias::maybe_value, safe_alias_of_v<SemiAwaitables>...}),- (must_await_immediately_v<SemiAwaitables> || ...)>;+ std::min(+ {safe_alias::maybe_value, lenient_safe_alias_of_v<SemiAwaitables>...}),+ (folly::ext::must_use_immediately_v<SemiAwaitables> || ...)>; } // namespace detail @@ -142,7 +144,7 @@ // co_await folly::coro::collectAll(doSomething(), doSomethingElse()); // template <typename... SemiAwaitables>-// Do NOT take awaitables by-reference, that would break `NowTask` safety.+// Do NOT take awaitables by-reference, that would break `now_task` safety. auto collectAll(SemiAwaitables... awaitables) -> detail::CollectAllTask<SemiAwaitables...>; @@ -559,11 +561,10 @@ // ranges::views::move); // template <typename InputRange>-auto collectAnyRange(InputRange awaitables)- -> folly::coro::Task<std::pair<- size_t,- folly::Try<detail::collect_all_range_component_t<- detail::range_reference_t<InputRange>>>>>;+auto collectAnyRange(InputRange awaitables) -> folly::coro::Task<std::pair<+ size_t,+ folly::Try<detail::collect_all_range_component_t<+ detail::range_reference_t<InputRange>>>>>; /////////////////////////////////////////////////////////////////////////// // collectAnyWithoutExceptionRange(RangeOf<SemiAwaitable<T>>&&)
@@ -16,11 +16,9 @@ #pragma once +#include <optional> #include <type_traits>--#if __has_include(<variant>) #include <variant>-#endif #include <folly/Portability.h> #include <folly/Utility.h>@@ -295,25 +293,79 @@ return t ? true : f ? false : coro::go().eager; } -class ExtendedCoroutineHandle;--// Extended promise interface folly::coro types are expected to implement-class ExtendedCoroutinePromise {- public:- // Types may provide a more efficient resumption path when they know they will- // be receiving an error result from the awaitee.- // If they do, they might also update the active stack frame.- virtual std::pair<ExtendedCoroutineHandle, AsyncStackFrame*> getErrorHandle(- exception_wrapper&) = 0;+template <typename>+class ExtendedCoroutinePromiseCrtp; - protected:- ~ExtendedCoroutinePromise() = default;-};+namespace detail {+template <typename, typename, typename>+class TaskPromiseWrapperBase;+} // Extended version of coroutine_handle<void> // Assumes (and enforces) assumption that coroutine_handle is a pointer class ExtendedCoroutineHandle {+ protected:+ template <typename>+ friend class ExtendedCoroutinePromiseCrtp;+ template <typename, typename, typename>+ friend class detail::TaskPromiseWrapperBase;+ // This passkey aims to stop end users from calling `getPromiseBase`, which+ // is an unsafe implementation detail, and to prevent overload ambiguity.+ //+ // It also doubles as the sigil for `use_extended_handle_concept`, another+ // private detail.+ class PrivateTag {+ private:+ friend ExtendedCoroutineHandle;+ PrivateTag() = default;+ };++ private:+ // SFINAE detection for the `use_extended_handle_concept` member type alias+ // that classes implementing `getErrorHandle` must expose. We don't want to+ // use any kind of common base on `TaskWrapperPromise`, be it non-empty+ // `PromiseBase`, or a dedicated empty tag, since either one would break+ // empty-base optimization.++ template <typename T>+ using use_extended_handle_of_ = typename T::use_extended_handle_concept;++ template <typename T, typename Void = void>+ struct use_extended_handle {+ static_assert(+ require_sizeof<T>, "`use_extended_handle` on incomplete type");+ static constexpr bool value = false;+ };++ template <typename T>+ struct use_extended_handle<T, void_t<use_extended_handle_of_<T>>> {+ static constexpr bool value =+ std::is_same_v<use_extended_handle_of_<T>, PrivateTag>;+ };+ public:+ using ErrorHandle = std::pair<ExtendedCoroutineHandle, AsyncStackFrame*>;++ class PromiseBase {+ private:+ friend class ExtendedCoroutineHandle;+ template <typename>+ friend class ExtendedCoroutinePromiseCrtp;++ using Fn = std::optional<ErrorHandle>(PromiseBase*, exception_wrapper& ex);++ explicit PromiseBase(Fn* fn) : getErrorHandlePtr_(fn) {}+ ~PromiseBase() = default;++ // A manual vtable with 1 function. Benefits over virtual inheritance:+ // - `TaskWrapperPromise` can implement `getErrorHandle` without bloating+ // itself with with a vtable it does not need.+ // - A tiny binary size win.+ // - Derived classes like `TaskPromise` don't have to be `final` in order+ // for the compiler to treat them as non-polymorphic.+ Fn* getErrorHandlePtr_;+ };+ template <typename Promise> /*implicit*/ ExtendedCoroutineHandle( coroutine_handle<Promise> handle) noexcept@@ -324,11 +376,10 @@ template < typename Promise,- std::enable_if_t<- std::is_base_of_v<ExtendedCoroutinePromise, Promise>,- int> = 0>- /*implicit*/ ExtendedCoroutineHandle(Promise* p) noexcept- : basic_(coroutine_handle<Promise>::from_promise(*p)), extended_(p) {}+ std::enable_if_t<use_extended_handle<Promise>::value, int> = 0>+ /*implicit*/ ExtendedCoroutineHandle(Promise* promise) noexcept+ : basic_(coroutine_handle<Promise>::from_promise(*promise)),+ extended_(Promise::getPromiseBase(PrivateTag{}, promise)) {} ExtendedCoroutineHandle() noexcept = default; @@ -338,10 +389,11 @@ coroutine_handle<> getHandle() const noexcept { return basic_; } - std::pair<ExtendedCoroutineHandle, AsyncStackFrame*> getErrorHandle(- exception_wrapper& ex) {+ ErrorHandle getErrorHandle(exception_wrapper& ex) { if (extended_) {- return extended_->getErrorHandle(ex);+ if (auto res = extended_->getErrorHandlePtr_(extended_, ex)) {+ return *res;+ } } return {basic_, nullptr}; }@@ -351,15 +403,55 @@ private: template <typename Promise> static auto fromBasic(coroutine_handle<Promise> handle) noexcept {- if constexpr (std::is_convertible_v<Promise*, ExtendedCoroutinePromise*>) {- return static_cast<ExtendedCoroutinePromise*>(&handle.promise());+ if constexpr (use_extended_handle<Promise>::value) {+ return Promise::getPromiseBase(PrivateTag{}, &handle.promise()); } else { return nullptr; } } coroutine_handle<> basic_;- ExtendedCoroutinePromise* extended_{nullptr};+ PromiseBase* extended_{nullptr};+};++// folly::coro types are expected to implement this extended promise interface.+//+// It allows types to provide a more efficient resumption path when they know+// they will be receiving an error result from the awaitee.+//+// First, publicly inherit from `ExtendedCoroutinePromiseCrtp<YourPromise>`,+// Second, implement this static method on `YourPromise`:+//+// static std::optional<ExtendedCoroutineHandle::ErrorHandle>+// getErrorHandleImpl(YourPromise&, exception_wrapper&);+//+// Return `std::nullopt` to avoid changing the resumption path. Otherwise,+// return the `ExtendedCoroutineHandle` to resume & the active stack frame.+//+// DANGER: `YourPromise& promise` is a promise instance, but it might NOT+// directly correspond to a coro frame. For example, if your coro is wrapped,+// that promise is a **member** inside a larger wrapper promise for the coro.+// Therefore, you must NOT call `coroutine_handle<...>::from_promise(promise)`.+// In the future, the true handle could be supplied, but none of the current+// coros required it.+template <typename Promise>+class ExtendedCoroutinePromiseCrtp+ : public ExtendedCoroutineHandle::PromiseBase {+ public:+ using use_extended_handle_concept = ExtendedCoroutineHandle::PrivateTag;++ static ExtendedCoroutineHandle::PromiseBase* getPromiseBase(+ ExtendedCoroutineHandle::PrivateTag, ExtendedCoroutinePromiseCrtp* me) {+ return me;+ }++ protected:+ using PromiseBase = typename ExtendedCoroutineHandle::PromiseBase;+ ExtendedCoroutinePromiseCrtp()+ : PromiseBase(+[](PromiseBase* p, exception_wrapper& ex) {+ return Promise::getErrorHandleImpl(*static_cast<Promise*>(p), ex);+ }) {}+ ~ExtendedCoroutinePromiseCrtp() = default; }; } // namespace folly::coro
@@ -0,0 +1,66 @@+/*+ * Copyright (c) Meta Platforms, Inc. and affiliates.+ *+ * Licensed under the Apache License, Version 2.0 (the "License");+ * you may not use this file except in compliance with the License.+ * You may obtain a copy of the License at+ *+ * http://www.apache.org/licenses/LICENSE-2.0+ *+ * Unless required by applicable law or agreed to in writing, software+ * distributed under the License is distributed on an "AS IS" BASIS,+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.+ * See the License for the specific language governing permissions and+ * limitations under the License.+ */++#pragma once++// KEEP THIS HEADER MINIMAL -- it is included by all `result` coro users.++#include <cassert>+#include <type_traits>++#include <folly/ExceptionWrapper.h>+#include <folly/OperationCancelled.h>++namespace folly::coro {++class co_error final {+ public:+ template <+ typename... A,+ std::enable_if_t<+ sizeof...(A) && std::is_constructible<exception_wrapper, A...>::value,+ int> = 0>+ explicit co_error(A&&... a) noexcept(+ std::is_nothrow_constructible<exception_wrapper, A...>::value)+ : ex_(static_cast<A&&>(a)...) {+ static_assert(+ sizeof...(A) != 1 ||+ !(std::is_same_v<std::decay_t<A>, OperationCancelled> && ...),+ "Use co_stopped_may_throw instead of co_error(OperationCancelled{})");+ assert(ex_);+ }++ const exception_wrapper& exception() const { return ex_; }++ exception_wrapper& exception() { return ex_; }++ private:+ exception_wrapper ex_;+};++class co_stopped_may_throw_t final {+ public:+ /* implicit */ operator co_error() const {+ return co_error(make_exception_wrapper<OperationCancelled>());+ }+};++inline constexpr co_stopped_may_throw_t co_stopped_may_throw{};++[[deprecated("Use co_stopped_may_throw")]]+inline constexpr co_stopped_may_throw_t co_cancelled{};++} // namespace folly::coro
@@ -73,7 +73,7 @@ co_await baton; } if (cancelled) {- co_yield co_cancelled;+ co_yield co_stopped_may_throw; } co_yield co_result(std::move(result)); }
@@ -19,6 +19,7 @@ #include <atomic> #include <type_traits> +#include <folly/coro/BlockingWait.h> #include <folly/coro/Coroutine.h> #include <folly/coro/GtestHelpers.h> #include <folly/coro/Result.h>@@ -109,13 +110,65 @@ } namespace detail {++// Matcher implementation that wraps a Task and delegates to an inner matcher+// (typically ::testing::Throws or ::testing::ThrowsMessage).+// This allows CoThrows/CoThrowsMessage to reuse GMock's exception matchers.+template <typename InnerMatcher>+class CoroExceptionMatcherImpl {+ public:+ explicit CoroExceptionMatcherImpl(InnerMatcher inner_matcher)+ : inner_matcher_(std::move(inner_matcher)),+ // Convert the inner matcher to a concrete Matcher type so we can+ // use its DescribeTo/DescribeNegationTo methods.+ callable_matcher_(inner_matcher_) {}++ void DescribeTo(std::ostream* os) const { callable_matcher_.DescribeTo(os); }++ void DescribeNegationTo(std::ostream* os) const {+ callable_matcher_.DescribeNegationTo(os);+ }++ template <typename T>+ bool MatchAndExplain(T&& x, ::testing::MatchResultListener* listener) const {+ // Task is move-only. GoogleTest passes values as const T&, but we need+ // to move the Task into blockingWait. We use const_cast to enable the+ // move. This is safe because each matcher instance is used only once.+ using MutableT = std::remove_const_t<std::remove_reference_t<T>>;+ auto& mutableX = const_cast<MutableT&>(x);++ // Wrap the task in a shared_ptr so the callable can be const-invoked+ // (GMock's Throws matcher requires the callable to be const-invocable).+ auto taskHolder =+ std::make_shared<std::optional<MutableT>>(std::move(mutableX));++ std::function<void()> callable = [taskHolder]() {+ if (!taskHolder->has_value()) {+ throw std::logic_error("Task already consumed");+ }+ auto task = std::move(taskHolder->value());+ taskHolder->reset();+ blockingWait(std::move(task));+ };++ // Delegate to the inner matcher (Throws/ThrowsMessage).+ // GMock's Throws matchers expect a callable, which they invoke to check+ // for exceptions.+ return callable_matcher_.MatchAndExplain(callable, listener);+ }++ private:+ InnerMatcher inner_matcher_;+ ::testing::Matcher<std::function<void()>> callable_matcher_;+};+ template <typename Fn> auto makeCoAction(Fn&& fn) { static_assert( std::is_copy_constructible_v<remove_cvref_t<Fn>>, "Fn should be copyable to allow calling mocked call multiple times."); - using Ret = std::invoke_result_t<remove_cvref_t<Fn>&&>;+ using Ret = invoke_result_t<remove_cvref_t<Fn>&&>; return ::testing::InvokeWithoutArgs( [fn = std::forward<Fn>(fn)]() mutable -> Ret { return co_invoke(fn); }); }@@ -243,6 +296,70 @@ return detail::makeCoAction([ex = std::forward<Ex>(e)]() -> Task<T> { co_yield co_error(ex); });+}++template <typename T>+auto CoStoppedMayThrow() {+ return detail::makeCoAction([]() -> Task<T> {+ co_yield co_stopped_may_throw;+ });+}++// CoThrows()+// CoThrows(exceptionMatcher)+// CoThrowsMessage(messageMatcher)+//+// These matchers accept a folly::coro::Task and verify that when awaited,+// it throws an exception with the given type and properties.+//+// Examples:+//+// EXPECT_THAT(+// []() -> folly::coro::Task<void> {+// throw std::runtime_error("message");+// co_return;+// }(),+// CoThrows<std::runtime_error>());+//+// EXPECT_THAT(+// []() -> folly::coro::Task<void> {+// throw std::runtime_error("message");+// co_return;+// }(),+// CoThrowsMessage<std::runtime_error>(HasSubstr("message")));+//+// EXPECT_THAT(+// []() -> folly::coro::Task<void> {+// throw std::runtime_error("message");+// co_return;+// }(),+// CoThrows<std::runtime_error>(+// Property(&std::runtime_error::what, HasSubstr("message"))));++template <typename Err>+auto CoThrows() {+ return ::testing::MakePolymorphicMatcher(+ detail::CoroExceptionMatcherImpl<decltype(::testing::Throws<Err>())>(+ ::testing::Throws<Err>()));+}++template <typename Err, typename ExceptionMatcher>+auto CoThrows(const ExceptionMatcher& exception_matcher) {+ return ::testing::MakePolymorphicMatcher(+ detail::CoroExceptionMatcherImpl<decltype(::testing::Throws<Err>(+ exception_matcher))>(::testing::Throws<Err>(exception_matcher)));+}++template <typename Err, typename MessageMatcher>+auto CoThrowsMessage(MessageMatcher&& message_matcher) {+ static_assert(+ std::is_base_of_v<std::exception, Err>,+ "expected an std::exception-derived type");+ return ::testing::MakePolymorphicMatcher(+ detail::CoroExceptionMatcherImpl<decltype(::testing::ThrowsMessage<Err>(+ std::forward<MessageMatcher>(message_matcher)))>(+ ::testing::ThrowsMessage<Err>(+ std::forward<MessageMatcher>(message_matcher)))); } } // namespace gmock_helpers
@@ -141,6 +141,7 @@ : public test_suite_name { \ public: \ GTEST_TEST_CLASS_NAME_(test_suite_name, test_name)() {} \+ ~GTEST_TEST_CLASS_NAME_(test_suite_name, test_name)() override = default; \ void TestBody() override; \ folly::coro::Task<void> co_TestBody(); \ \@@ -162,8 +163,13 @@ static int gtest_registering_dummy_ [[maybe_unused]]; \ GTEST_TEST_CLASS_NAME_(test_suite_name, test_name) \ (const GTEST_TEST_CLASS_NAME_(test_suite_name, test_name) &) = delete; \+ GTEST_TEST_CLASS_NAME_(test_suite_name, test_name) \+ (GTEST_TEST_CLASS_NAME_(test_suite_name, test_name) &&) = delete; \ GTEST_TEST_CLASS_NAME_(test_suite_name, test_name) & operator=( \ const GTEST_TEST_CLASS_NAME_(test_suite_name, test_name) &) = \+ delete; /* NOLINT */ \+ GTEST_TEST_CLASS_NAME_(test_suite_name, test_name) & operator=( \+ GTEST_TEST_CLASS_NAME_(test_suite_name, test_name) &&) = \ delete; /* NOLINT */ \ }; \ int GTEST_TEST_CLASS_NAME_( \
@@ -1,299 +0,0 @@-/*- * Copyright (c) Meta Platforms, Inc. and affiliates.- *- * Licensed under the Apache License, Version 2.0 (the "License");- * you may not use this file except in compliance with the License.- * You may obtain a copy of the License at- *- * http://www.apache.org/licenses/LICENSE-2.0- *- * Unless required by applicable law or agreed to in writing, software- * distributed under the License is distributed on an "AS IS" BASIS,- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.- * See the License for the specific language governing permissions and- * limitations under the License.- */--#pragma once--#include <folly/coro/TaskWrapper.h>-#include <folly/coro/ViaIfAsync.h>--#if FOLLY_HAS_COROUTINES--/// ## When to use this-///-/// Use `AsNoexcept<>` only with APIs that only take coroutines that MUST NOT-/// throw when awaited -- like `co_cleanup()` or async scopes. If your code-/// compiles without `AsNoexcept<>`, you do not need it!-///-/// ## This is probably not the utility you are looking for!-///-/// - This is not related to `co_nothrow`, which is a perf optimization for-/// coroutines with hot exceptions. `co_await co_nothrow(foo())` means-/// "when `foo()` throws, exit the current coro and pass the exception to-/// whatever is awaiting me". This saves the ~usec overhead of rethrow.-///-/// - If your project likes to avoid exceptions, that is not a great reason-/// to reflexively make all your coros `AsNoexcept<>`, for these reasons:-/// * Since `AsNoexcept<>` is implemented as a wrapper, it may reduce-/// your build speed.-/// * Coro frame allocation & construction can still throw (unless you-/// also mark the coro function `noexcept`).-/// * Emitting the `noexcept` -> `std::terminate` offramp can sometimes-/// be a pessimization compared to normal exception propagation.-///-/// - We do not provide a helper like `co_await co_fatalOnThrow(...)`, since-/// most callsites should either handle the exception (possibly with-/// `co_awaitTry`), or let it fly.-///-/// ## Why does this even exist, what's wrong with `noexcept`?-///-/// Some `folly::coro` libraries require `AsNoexcept<>` is to clearly signal a-/// **firm contract** between the API and the user-supplied coroutine. This is-/// only appropriate in situations similar to sync destructors, where the API-/// has no good recourse in case of a thrown exception.-///-/// We need a special wrapper type because marking a coroutine function-/// `noexcept` says nothing about whether awaiting the resulting coroutine can-/// throw. Rather, it makes fatal any exception thrown during the construction-/// of the coroutine object itself (i.e. a `bad_alloc` for the frame, or-/// errors copying/moving the args).-///-/// ## How exactly does `AsNoexcept<>` work?-///-/// `Noexcept.h` lets you mark coroutine types as `noexcept_awaitable_v`:-///-/// []() -> AsNoexcept<Task<T>, OnCancel(defaultT())> { co_return ...; }-///-/// This function creates a coroutine whose awaitable is that of the inner-/// task, but wrapped with `detail::NoexceptAwaitable<...>`.-//-/// The latter is an awaitable-wrapper similar to `co_awaitTry()`, except that-/// it terminates the program if `someAwaitable()` resumes with a thrown-/// exception. So, both of these will never throw, but the former returns a-/// `Try` while latter returns an unwrapped value:-///-/// co_await co_awaitTry(intTask()) // `Try<int>`-/// co_await detail::NoexceptAwaitable<int, OnCancel(0)>{intTask()} // `int`-///-/// Both the coroutine `AsNoexcept<Task<...>, ...>` and the preceding 2-/// awaitables return `true` for `noexcept_awaitable_v`.-///-/// `AsNoexcept<>` / `NoexceptAwaitable<>` compose properly with other coro--/// and awaitable-wrappers. But, not all combinations make sense -- see the-/// test, and/or extend it if needed. For example, the outer wrapper is-/// useless in `NoexceptAwaitable<...>(co_awaitTry(...))`, since exceptions-/// would already have been routed into a `Try`.--namespace folly::coro {--struct TerminateOnCancel {};-inline constexpr TerminateOnCancel terminateOnCancel{};--template <typename T>-struct OnCancel {- T privateVal_; // only `public` to make this a structural type- T onCancelDefaultValue() const noexcept {- static_assert(std::is_nothrow_copy_constructible_v<T>);- return privateVal_;- }- consteval explicit OnCancel(T t) : privateVal_{std::move(t)} {}-};--template <>-struct OnCancel<void> {- void onCancelDefaultValue() const noexcept {}- consteval explicit OnCancel() = default;-};--namespace detail {--template <typename Awaitable, auto CancelCfg>-class NoexceptAwaiter {- private:- using Awaiter = awaiter_type_t<Awaitable>;- Awaiter awaiter_;-- public:- explicit NoexceptAwaiter(Awaitable&& awaiter)- : awaiter_(get_awaiter(static_cast<Awaitable&&>(awaiter))) {}-- auto await_ready() noexcept -> decltype(awaiter_.await_ready()) {- // As of this writing, all `await_ready` in `folly::coro` are `noexcept`.- // If this is legitimately triggered, then we can decide the right policy.- static_assert(noexcept(awaiter_.await_ready()));- return awaiter_.await_ready();- }-- // `noexcept` forces any rethrown exceptions to `std::terminate`- auto await_resume() noexcept -> decltype(awaiter_.await_resume()) {- if constexpr (std::is_same_v<decltype(CancelCfg), TerminateOnCancel>) {- return awaiter_.await_resume();- } else {- try {- return awaiter_.await_resume();- } catch (const OperationCancelled&) {- // IMPORTANT: If you want to extend this protocol to pull out a default- // value from the awaiter, be sure to add this assert:- // static_assert(noexcept(CancelCfg.onCancelDefaultValue(awaiter_)));- return CancelCfg.onCancelDefaultValue();- }- }- }-- // `noexcept` here as well, because the underlying awaitable might- // have a throwing `await_suspend`, and those exceptions propagate- // to the parent coro promise, bypassing `await_resume`.- // Demo: https://godbolt.org/z/Edfj8P8be- template <typename Promise>- auto await_suspend(coroutine_handle<Promise> coro) noexcept- -> decltype(awaiter_.await_suspend(coro)) {- return awaiter_.await_suspend(coro);- }-};--template <typename, auto>-class NoexceptAwaitable;--template <auto CancelCfg>-struct NoexceptAwaitableWithCancelCfg {- template <typename T>- using apply = NoexceptAwaitable<T, CancelCfg>;-};--template <typename T, auto CancelCfg>-class [[FOLLY_ATTR_CLANG_CORO_AWAIT_ELIDABLE]] NoexceptAwaitable- : public CommutativeWrapperAwaitable<- NoexceptAwaitableWithCancelCfg<CancelCfg>::template apply,- T> {- public:- using CommutativeWrapperAwaitable<- NoexceptAwaitableWithCancelCfg<CancelCfg>::template apply,- T>::CommutativeWrapperAwaitable;-- template <typename T2 = T, std::enable_if_t<is_awaitable_v<T2>, int> = 0>- NoexceptAwaiter<T, CancelCfg> operator co_await() && {- return NoexceptAwaiter<T, CancelCfg>{std::move(this->inner_)};- }-- using folly_private_noexcept_awaitable_t = std::true_type;-};--} // namespace detail--#if FOLLY_HAS_IMMOVABLE_COROUTINES--template <typename Inner, auto CancelCfg>-class AsNoexcept;-// NB: While it'd be prettier to have `AsNoexcept` branch on whether the inner-// task has an executor, a separate template is much simpler.-template <typename Inner, auto CancelCfg>-class AsNoexceptWithExecutor;--namespace detail {-template <typename Inner, auto CancelCfg>-struct AsNoexceptWithExecutorCfg {- using InnerTaskWithExecutorT = Inner;- using WrapperTaskT = AsNoexcept<- typename Inner::folly_private_task_without_executor_t,- CancelCfg>;- template <typename Awaitable> // library-internal, meant to be by-rref- static inline auto wrapAwaitable(Awaitable&& awaitable) noexcept {- // Assert can be removed, I was concerned if we accidentally double-wrap- static_assert(!noexcept_awaitable_v<Awaitable>);- return detail::NoexceptAwaitable<Awaitable, CancelCfg>{- mustAwaitImmediatelyUnsafeMover(static_cast<Awaitable&&>(awaitable))()};- }-};-template <typename Inner, auto CancelCfg>-using AsNoexceptWithExecutorBase = TaskWithExecutorWrapperCrtp<- AsNoexceptWithExecutor<Inner, CancelCfg>,- AsNoexceptWithExecutorCfg<Inner, CancelCfg>>;-} // namespace detail--template <typename Inner, auto CancelCfg = OnCancel<void>{}>-class FOLLY_NODISCARD AsNoexceptWithExecutor final- : public detail::AsNoexceptWithExecutorBase<Inner, CancelCfg> {- protected:- using detail::AsNoexceptWithExecutorBase<Inner, CancelCfg>::- AsNoexceptWithExecutorBase;-- public:- using folly_private_noexcept_awaitable_t = std::true_type;-};--namespace detail {--template <typename... BaseArgs>-class AsNoexceptTaskPromiseWrapper final- : public TaskPromiseWrapper<BaseArgs...> {};--template <typename Inner, auto CancelCfg>-struct AsNoexceptCfg {- using ValueT = semi_await_result_t<Inner>;- using InnerTaskT = Inner;- using TaskWithExecutorT = AsNoexceptWithExecutor<- decltype(co_withExecutor(- FOLLY_DECLVAL(Executor::KeepAlive<>), FOLLY_DECLVAL(Inner))),- CancelCfg>;- using PromiseT = AsNoexceptTaskPromiseWrapper<- ValueT,- AsNoexcept<Inner, CancelCfg>,- typename folly::coro::coroutine_traits<Inner>::promise_type>;- template <typename Awaitable> // library-internal, meant to be by-rref- static inline auto wrapAwaitable(Awaitable&& awaitable) noexcept {- // Assert can be removed, I was concerned if we accidentally double-wrap- static_assert(!noexcept_awaitable_v<Awaitable>);- return detail::NoexceptAwaitable<Awaitable, CancelCfg>{- static_cast<Awaitable&&>(awaitable)};- }-};--template <typename Inner, auto CancelCfg>-using AsNoexceptBase = TaskWrapperCrtp<- AsNoexcept<Inner, CancelCfg>,- AsNoexceptCfg<Inner, CancelCfg>>;--// CAUTION: `as_noexcept_rewrapper` gives you the power to wrap and unwrap-// `AsNoexcept`, so you must be extremely careful to preserve behavior:-// - The unwrapped task must be rewrapped before awaiting.-// - You must not wrap any other task.--template <typename>-struct as_noexcept_rewrapper {- static inline constexpr bool as_noexcept_wrapped = false;- static auto wrap_with(auto fn) { return fn(); }-};--template <typename Inner, auto Cfg>-struct as_noexcept_rewrapper<AsNoexcept<Inner, Cfg>> {- static inline constexpr bool as_noexcept_wrapped = true;- static Inner unwrapTask(AsNoexcept<Inner, Cfg>&& t) {- return std::move(t).unwrapTask();- }- static auto wrap_with(auto fn) {- return AsNoexcept<decltype(fn()), Cfg>{fn()};- }-};--} // namespace detail--template <typename Inner, auto CancelCfg = OnCancel<void>{}>-class FOLLY_CORO_TASK_ATTRS AsNoexcept final- : public detail::AsNoexceptBase<Inner, CancelCfg> {- protected:- using detail::AsNoexceptBase<Inner, CancelCfg>::AsNoexceptBase;-- template <typename> // Can unwrap and re-wrap (construct)- friend struct detail::as_noexcept_rewrapper;-- public:- using folly_private_noexcept_awaitable_t = std::true_type;-};--#endif // FOLLY_HAS_IMMOVABLE_COROUTINES--} // namespace folly::coro--#endif // FOLLY_HAS_COROUTINES
@@ -0,0 +1,166 @@+/*+ * Copyright (c) Meta Platforms, Inc. and affiliates.+ *+ * Licensed under the Apache License, Version 2.0 (the "License");+ * you may not use this file except in compliance with the License.+ * You may obtain a copy of the License at+ *+ * http://www.apache.org/licenses/LICENSE-2.0+ *+ * Unless required by applicable law or agreed to in writing, software+ * distributed under the License is distributed on an "AS IS" BASIS,+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.+ * See the License for the specific language governing permissions and+ * limitations under the License.+ */++#pragma once++#include <folly/ExceptionWrapper.h>+#include <folly/Portability.h> // FOLLY_HAS_COROUTINES+#include <folly/coro/ViaIfAsync.h>++// For DCHECK, `ViaIfAsync.h` depends on it already.+#include <glog/logging.h>++#if FOLLY_HAS_COROUTINES++namespace folly::coro {++namespace detail {++template <typename T>+class [[FOLLY_ATTR_CLANG_CORO_AWAIT_ELIDABLE]] NothrowAwaitable;++// The `!value_only_awaitable_v` constraint stops `co_nothrow()` from wrapping+// `value_or_error_or_stopped`, `co_awaitTry`, `AsNoexcept`, etc.+//+// Rationale: Instead, we could do:+// - (not very useful) Nothing -- modeling the behavior that the exception+// was already captured by the inner type.+// - (highly unexpected / buggy) Prevent the inner type from capturing the+// exception, and force its propagation up-stack.+// This was banned because real-world users didn't uniformly expect one of the+// behaviors over the other, and nobody really **needs** this to work.+template <typename T>+class [[FOLLY_ATTR_CLANG_CORO_AWAIT_ELIDABLE]]+NothrowAwaitable : public CommutativeWrapperAwaitable<NothrowAwaitable, T> {+ public:+ using CommutativeWrapperAwaitable<NothrowAwaitable, T>::+ CommutativeWrapperAwaitable;++ template <+ typename T2 = T,+ std::enable_if_t<!value_only_awaitable_v<T>, int> = 0>+ T2&& unwrap() {+ return std::move(this->inner_);+ }+};++template <typename>+class ValueOrError;+template <typename>+class ValueOrErrorImpl;++// Mixin supporting `co_nothrow()` and `value_or_error()` for tasks & generators+class BypassExceptionThrowing {+ private:+ enum class BypassMode : uint8_t {+ // Default state for `co_await task()`. The corresponding+ // `await_transform` calls must call `maybeActivate()`, which will either+ // promote `REQUESTED` to `ACTIVE`, or reset any `ACTIVE` state from a+ // prior suspension point back to `INACTIVE`.+ INACTIVE,+ // State after `await_transform` with a `co_nothrow` awaitable.+ ACTIVE,+ // State before `await_transform` with a `co_nothrow` awaitable.+ REQUESTED,+ // State for `value_or_error()` only allowing for nothrow-propagation of+ // `OperationCancelled` to the parent.+ ONLY_WHEN_OPERATION_CANCELLED,+ } bypassMode_{BypassMode::INACTIVE};++ protected:+ // This write interface is protected to alert future authors -- this is a+ // TIGHTLY COUPLED DETAIL. See the analogous note in `BasePromise`.+ template <typename>+ friend class BasePromise;++ template <typename Awaitable>+ void maybeActivate() {+ if (is_instantiation_of_v<ValueOrErrorImpl, Awaitable>) {+ DCHECK(+ // normal `value_or_error`: bypass `OperationCancelled` handling.+ bypassMode_ == BypassMode::ONLY_WHEN_OPERATION_CANCELLED ||+ // inner awaitable is value-only, no bypass needed.+ bypassMode_ == BypassMode::INACTIVE);+ } else {+ // Awaitable should've been unwrapped before getting here.+ static_assert(+ !is_instantiation_of_v<NothrowAwaitable, Awaitable> &&+ !is_instantiation_of_v<ValueOrError, Awaitable>);+ bypassMode_ = bypassMode_ == BypassMode::REQUESTED+ ? BypassMode::ACTIVE+ : BypassMode::INACTIVE;+ }+ }++ // Implements `co_nothrow` -- this gets called only from the matching+ // `await_transform(NothrowAwaitable<Awaitable>)`. The subsequent+ // `await_transform(Awaitable)` maps `REQUESTED` to `ACTIVE.+ template <typename Awaitable>+ void requestDueToNothrow() {+ // `co_nothrow` is incompatible with noexcept-awaitables, doc above.+ static_assert(!value_only_awaitable_v<Awaitable>);+ bypassMode_ = BypassMode::REQUESTED;+ }++ template <typename Awaitable>+ void requestDueToValueOrError() {+ // If the inner awaitable is value-only (e.g., `value_or_fatal`), it+ // already guarantees no exceptions escape. Do NOT activate bypass mode,+ // as that would intercept exceptions before the inner awaitable's+ // `await_resume_result()` can apply its policy.+ if constexpr (!value_only_awaitable_v<Awaitable>) {+ bypassMode_ = BypassMode::ONLY_WHEN_OPERATION_CANCELLED;+ }+ }++ public: // Otherwise we'd also need to friend `AsyncGenerator`, etc+ bool shouldBypassFor(exception_wrapper& ex) {+ return (bypassMode_ == BypassMode::ACTIVE) ||+ (bypassMode_ == BypassMode::ONLY_WHEN_OPERATION_CANCELLED &&+ // Today, this incurs RTTI cost, but once we migrate coros to use+ // `rich_exception_ptr`, this will often be RTTI-free.+ ex.get_exception<folly::OperationCancelled>());+ }+};++} // namespace detail++template <+ typename Awaitable,+ std::enable_if_t<+ !value_only_awaitable_v<Awaitable> && // Comment on `NothrowAwaitable`+ !folly::ext::must_use_immediately_v<Awaitable>,+ int> = 0>+detail::NothrowAwaitable<remove_cvref_t<Awaitable>> co_nothrow(+ [[FOLLY_ATTR_CLANG_CORO_AWAIT_ELIDABLE_ARGUMENT]] Awaitable&& awaitable) {+ return detail::NothrowAwaitable<remove_cvref_t<Awaitable>>{+ static_cast<Awaitable&&>(awaitable)};+}+template <+ typename Awaitable,+ std::enable_if_t<+ !value_only_awaitable_v<Awaitable> && // Comment on `NothrowAwaitable`+ folly::ext::must_use_immediately_v<Awaitable>,+ int> = 0>+detail::NothrowAwaitable<remove_cvref_t<Awaitable>> co_nothrow(+ [[FOLLY_ATTR_CLANG_CORO_AWAIT_ELIDABLE_ARGUMENT]] Awaitable awaitable) {+ return detail::NothrowAwaitable<remove_cvref_t<Awaitable>>{+ folly::ext::must_use_immediately_unsafe_mover(std::move(awaitable))()};+}++} // namespace folly::coro++#endif // FOLLY_HAS_COROUTINES
@@ -222,8 +222,24 @@ */ Future() = default; - Future(Future&&) noexcept = default;- Future& operator=(Future&&) noexcept = default;+ Future(Future&& other) noexcept+ : cs_(std::move(other.cs_)),+ state_(std::exchange(other.state_, nullptr)),+ ct_(std::move(other.ct_)),+ hasCancelTokenOverride_(+ std::exchange(other.hasCancelTokenOverride_, false)) {}++ Future& operator=(Future&& other) noexcept {+ if (this != &other) {+ cs_ = std::move(other.cs_);+ state_ = std::exchange(other.state_, nullptr);+ ct_ = std::move(other.ct_);+ hasCancelTokenOverride_ =+ std::exchange(other.hasCancelTokenOverride_, false);+ }+ return *this;+ }+ Future(const Future&) = delete; Future& operator=(const Future&) = delete; @@ -258,6 +274,10 @@ return WaitOperation{*this}; } + bool valid() const noexcept { return state_ != nullptr; }++ explicit operator bool() const noexcept { return valid(); }+ bool isReady() const noexcept { return state_->ready.ready(); } friend Future co_withCancellation(@@ -268,7 +288,8 @@ return std::move(future); } - using folly_private_safe_alias_t = safe_alias_of<T>;+ template <safe_alias Default>+ using folly_private_safe_alias_t = safe_alias_of<T, Default>; private: Future(CancellationSource cs, detail::PromiseState<T>& state)
@@ -1,114 +0,0 @@-/*- * Copyright (c) Meta Platforms, Inc. and affiliates.- *- * Licensed under the Apache License, Version 2.0 (the "License");- * you may not use this file except in compliance with the License.- * You may obtain a copy of the License at- *- * http://www.apache.org/licenses/LICENSE-2.0- *- * Unless required by applicable law or agreed to in writing, software- * distributed under the License is distributed on an "AS IS" BASIS,- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.- * See the License for the specific language governing permissions and- * limitations under the License.- */--#pragma once--#include <folly/Executor.h>-#include <folly/coro/Result.h>-#include <folly/coro/WithAsyncStack.h>--/// Use `co_ready` to "await" synchronous coroutine types from inside async-/// coroutines like `coro::Task`. For example:-///-/// result<int> getN();-/// int n = co_await co_ready(getN());-///-/// Also see `co_await_result` (`AwaitResult.h`) and `co_result` (`Result.h`).-///-/// If you need to optimize away ALL exception throwing in **async** code,-/// `co_ready` is not your top choice. In a `Task` coro:-///-/// auto v = co_await co_nothrow(asyncMayError()); // best practice-/// auto v = co_await co_ready(co_await_result(asyncMayError())); // too long-///-/// However, when you are calling synchronous `result` functions, or need to-/// efficiently handle **some** async errors, `co_ready` is your friend:-///-/// auto res = syncResultFn(); // or `co_await co_await_result(asyncFn())`-/// if (auto* ex = get_exception<MyError>(res)) {-/// /* handle ex */-/// } else {-/// auto v = co_await co_ready(std::move(res)); // propagate unhandled-/// }-///-/// This pattern has a few good properties:-/// - Easy error handling -- extracts the value from its argument, or-/// short-circuit any error to current coro's awaiter.-/// - Unlike `catch (const std::exception& ex)`, won't catch (and therefore-/// break) cancellation.-/// - The error path is MUCH more efficient (3-30 nanoseconds) than-/// `value_or_throw()` (1 microsecond).-///-/// We don't support `co_await syncResultFn()` to avoids confusion about which-/// parts of the code are sync vs async. The distinction is critical, since-/// one must not hold non-coro mutexes across async suspend points.-///-/// Future:-/// - Adding `std::ref` / `std::cref`, and possibly `folly::rref` variants of-/// this (as in `result.h`) might improve performance in hot code.-/// - The current implementation is `result`-only. If you have a need, it-/// would be fine to add the analogous specialization for `Try`. Just be-/// mindful of its two warts: empty state and empty `exception_wrapper`.--namespace folly::coro {--template <typename>-class co_ready;--#if FOLLY_HAS_RESULT--template <typename T>-class co_ready<result<T>> {- private:- result<T> res_;-- public:- explicit co_ready(result<T>&& res) : res_(std::move(res)) {}-- bool await_ready() const noexcept { return res_.has_value(); }-- auto await_resume() noexcept -> decltype(std::move(res_).value_or_throw()) {- return std::move(res_).value_or_throw();- }-- template <typename Promise>- auto await_suspend(- std::coroutine_handle<Promise> awaitingCoroutine) noexcept {- auto& promise = awaitingCoroutine.promise();- // We have to use the legacy API because (1) `folly::coro` internals still- // model cancellation as an exception, (2) to use `co_cancelled` here we'd- // have to check `res_` for `OperationCancelled` which can cost 50-100ns+.- auto awaiter = promise.yield_value(co_error(- std::move(res_).non_value().get_legacy_error_or_cancellation()));- return awaiter.await_suspend(awaitingCoroutine);- }-- friend auto co_viaIfAsync(- const Executor::KeepAlive<>&, co_ready&& r) noexcept {- return std::move(r);- }-- friend auto tag_invoke(cpo_t<co_withAsyncStack>, co_ready&& r) noexcept {- return std::move(r);- }-};--template <typename T>-co_ready(result<T>&&) -> co_ready<result<T>>;--#endif // FOLLY_HAS_RESULT--} // namespace folly::coro
@@ -19,34 +19,12 @@ #include <cassert> #include <type_traits> -#include <folly/ExceptionWrapper.h>-#include <folly/OperationCancelled.h> #include <folly/Try.h>-#include <folly/result/result.h>--namespace folly {-namespace coro {--class co_error final {- public:- template <- typename... A,- std::enable_if_t<- sizeof...(A) && std::is_constructible<exception_wrapper, A...>::value,- int> = 0>- explicit co_error(A&&... a) noexcept(- std::is_nothrow_constructible<exception_wrapper, A...>::value)- : ex_(static_cast<A&&>(a)...) {- assert(ex_);- }-- const exception_wrapper& exception() const { return ex_; }-- exception_wrapper& exception() { return ex_; }+#include <folly/coro/Error.h> // compat: used to be the same header+#include <folly/result/try.h>+#include <folly/result/value_only_result.h> - private:- exception_wrapper ex_;-};+namespace folly::coro { template <typename T> class co_result final {@@ -58,12 +36,18 @@ } #if FOLLY_HAS_RESULT- // Covered in `AwaitResultTest.cpp`, unlike the rest of this file, which is+ // Covered in `ValueOrErrorTest.cpp`, unlike the rest of this file, which is // covered in `TaskTest.cpp`. template <std::same_as<folly::result<T>> U> // no implicit ctors for `result` explicit co_result(U result) noexcept( std::is_nothrow_move_constructible<T>::value) : co_result(result_to_try(std::move(result))) {}++ // value_only_result always has a value, so conversion to Try is simple.+ template <std::same_as<folly::value_only_result<T>> U>+ explicit co_result(U valueOnlyResult) noexcept(+ std::is_nothrow_move_constructible<T>::value)+ : result_(std::move(valueOnlyResult).value_or_throw()) {} #endif const Try<T>& result() const { return result_; }@@ -77,16 +61,8 @@ #if FOLLY_HAS_RESULT template <typename T> co_result(result<T>) -> co_result<T>;+template <typename T>+co_result(value_only_result<T>) -> co_result<T>; #endif -class co_cancelled_t final {- public:- /* implicit */ operator co_error() const {- return co_error(OperationCancelled{});- }-};--inline constexpr co_cancelled_t co_cancelled{};--} // namespace coro-} // namespace folly+} // namespace folly::coro
@@ -128,7 +128,7 @@ const auto& cancelToken = co_await co_current_cancellation_token; if (cancelToken.isCancellationRequested()) {- co_yield folly::coro::co_error(OperationCancelled{});+ co_yield folly::coro::co_stopped_may_throw; } --retriesRemaining_;@@ -200,8 +200,8 @@ ? std::exp(dist{0., relativeJitterStdDev_}(randomGen_)) : 1.; // TODO T186551522 Calculate backoff in microseconds.- const auto backoffNominal =- Duration(folly::constexpr_clamp_cast<Duration::rep>(+ const auto backoffNominal = Duration(+ folly::constexpr_clamp_cast<Duration::rep>( jitter * minBackoff_.count() * std::pow(2, retryCount_ - 1u))); const Duration backoff = std::clamp(@@ -212,7 +212,7 @@ /// Check to see if we were cancelled during the sleep. const auto& cancelToken = co_await co_current_cancellation_token; if (cancelToken.isCancellationRequested()) {- co_yield folly::coro::co_cancelled;+ co_yield folly::coro::co_stopped_may_throw; } }
@@ -71,6 +71,11 @@ } } + PollFuture(const PollFuture&) = delete;+ PollFuture& operator=(const PollFuture&) = delete;+ PollFuture(PollFuture&&) = delete;+ PollFuture& operator=(PollFuture&&) = delete;+ Poll poll(Waker waker) { while (true) { std::queue<Func> funcs;
@@ -141,8 +141,9 @@ template <typename Awaitable> auto await_transform(Awaitable&& awaitable) {- return folly::coro::co_withAsyncStack(folly::coro::co_viaIfAsync(- executor_.get_alias(), static_cast<Awaitable&&>(awaitable)));+ return folly::coro::co_withAsyncStack(+ folly::coro::co_viaIfAsync(+ executor_.get_alias(), static_cast<Awaitable&&>(awaitable))); } folly::AsyncStackFrame& getAsyncFrame() noexcept {
@@ -36,7 +36,7 @@ namespace detail { template <typename Mutex, typename Policy>-class FOLLY_NODISCARD LockBase {+class [[nodiscard]] LockBase { public: static_assert(std::is_same_v< bool,
@@ -551,7 +551,13 @@ static constexpr std::size_t kUpgradeLockFlag = 2; static constexpr std::size_t kSharedLockCountIncrement = 4; - folly::Synchronized<State, folly::SpinLock> state_;+ struct ExclusiveMutex {+ std::unique_ptr<std::mutex> impl_{std::make_unique<std::mutex>()};+ void lock() { return impl_->lock(); }+ void unlock() { return impl_->unlock(); }+ };+ using Mutex = std::conditional_t<kIsSanitizeThread, ExclusiveMutex, SpinLock>;+ folly::Synchronized<State, Mutex> state_; }; inline SharedMutexFair::LockOperation<SharedMutexFair::LockAwaiter>
@@ -72,6 +72,13 @@ */ bool isFulfilled() const; + /*+ * Returns either an optional holding the result or an empty optional+ * depending on whether or not (respectively) the promise has been+ * fulfilled (i.e., `isFulfilled() == true`).+ */+ std::optional<TryType> poll() const;+ /** * Sets an exception in the promise. */@@ -148,6 +155,13 @@ template <typename T> bool SharedPromise<T>::isFulfilled() const { return state_.withRLock([](auto& state) { return isFulfilled(state); });+}++template <typename T>+auto SharedPromise<T>::poll() const -> std::optional<TryType> {+ return state_.withRLock([](const auto& state) {+ return isFulfilled(state) ? std::make_optional(state.result) : std::nullopt;+ }); } template <typename T>
@@ -73,7 +73,7 @@ }); co_await c.baton; if (cancelled) {- co_yield co_cancelled;+ co_yield co_stopped_may_throw; } } buffer_ = queue_.getMessages();
@@ -190,10 +190,10 @@ } template <typename FuncT>- using rlock_result_t = std::invoke_result_t<FuncT, ReadLockedPtr>;+ using rlock_result_t = invoke_result_t<FuncT, ReadLockedPtr>; template <typename FuncT>- using wlock_result_t = std::invoke_result_t<FuncT, WriteLockedPtr>;+ using wlock_result_t = invoke_result_t<FuncT, WriteLockedPtr>; template <typename FuncT, typename ReturnT = rlock_result_t<FuncT>> typename std::enable_if<!is_semi_awaitable_v<ReturnT>, Task<ReturnT>>::type
@@ -33,9 +33,8 @@ #include <folly/ScopeGuard.h> #include <folly/Traits.h> #include <folly/Try.h>-#include <folly/coro/AwaitImmediately.h>+#include <folly/coro/BasePromise.h> #include <folly/coro/Coroutine.h>-#include <folly/coro/CurrentExecutor.h> #include <folly/coro/Invoke.h> #include <folly/coro/Result.h> #include <folly/coro/ScopeExit.h>@@ -49,6 +48,7 @@ #include <folly/futures/Future.h> #include <folly/io/async/Request.h> #include <folly/lang/Assume.h>+#include <folly/lang/MustUseImmediately.h> #include <folly/lang/SafeAlias-fwd.h> #include <folly/result/result.h> #include <folly/result/try.h>@@ -74,17 +74,16 @@ TaskPromisePrivate() = default; }; -class TaskPromiseBase {+class TaskPromiseBase : public BasePromise<> { static TaskPromisePrivate privateTag() { return TaskPromisePrivate{}; } + protected: class FinalAwaiter { public: bool await_ready() noexcept { return false; } template <typename Promise>- FOLLY_CORO_AWAIT_SUSPEND_NONTRIVIAL_ATTRIBUTES coroutine_handle<>- await_suspend(coroutine_handle<Promise> coro) noexcept {- auto& promise = coro.promise();+ coroutine_handle<> await_suspend_promise(Promise& promise) noexcept { // If ScopeExitTask has been attached, then we expect that the // ScopeExitTask will handle the lifetime of the async stack. See // ScopeExitTaskPromise's FinalAwaiter for more details.@@ -114,24 +113,18 @@ return promise.continuationRef(privateTag()).getHandle(); } + template <typename Promise>+ FOLLY_CORO_AWAIT_SUSPEND_NONTRIVIAL_ATTRIBUTES coroutine_handle<>+ await_suspend(coroutine_handle<Promise> coro) noexcept {+ return await_suspend_promise(coro.promise());+ }+ [[noreturn]] void await_resume() noexcept { folly::assume_unreachable(); } }; - friend class FinalAwaiter;-- protected: TaskPromiseBase() noexcept = default; ~TaskPromiseBase() = default; - template <typename Promise>- variant_awaitable<FinalAwaiter, ready_awaitable<>> do_safe_point(- Promise& promise) noexcept {- if (cancelToken_.isCancellationRequested()) {- return promise.yield_value(co_cancelled);- }- return ready_awaitable<>{};- }- public: static void* operator new(std::size_t size) { return ::folly_coro_async_malloc(size);@@ -145,60 +138,6 @@ FinalAwaiter final_suspend() noexcept { return {}; } - template <- typename Awaitable,- std::enable_if_t<!must_await_immediately_v<Awaitable>, int> = 0>- auto await_transform(Awaitable&& awaitable) {- bypassExceptionThrowing_ =- bypassExceptionThrowing_ == BypassExceptionThrowing::REQUESTED- ? BypassExceptionThrowing::ACTIVE- : BypassExceptionThrowing::INACTIVE;-- return folly::coro::co_withAsyncStack(folly::coro::co_viaIfAsync(- executor_.get_alias(),- folly::coro::co_withCancellation(- cancelToken_, static_cast<Awaitable&&>(awaitable))));- }- template <- typename Awaitable,- std::enable_if_t<must_await_immediately_v<Awaitable>, int> = 0>- auto await_transform(Awaitable awaitable) {- bypassExceptionThrowing_ =- bypassExceptionThrowing_ == BypassExceptionThrowing::REQUESTED- ? BypassExceptionThrowing::ACTIVE- : BypassExceptionThrowing::INACTIVE;-- return folly::coro::co_withAsyncStack(folly::coro::co_viaIfAsync(- executor_.get_alias(),- folly::coro::co_withCancellation(- cancelToken_,- mustAwaitImmediatelyUnsafeMover(std::move(awaitable))())));- }-- template <typename Awaitable>- auto await_transform(NothrowAwaitable<Awaitable> awaitable) {- bypassExceptionThrowing_ = BypassExceptionThrowing::REQUESTED;- return await_transform(- mustAwaitImmediatelyUnsafeMover(awaitable.unwrap())());- }-- auto await_transform(co_current_executor_t) noexcept {- return ready_awaitable<folly::Executor*>{executor_.get()};- }-- auto await_transform(co_current_cancellation_token_t) noexcept {- return ready_awaitable<const folly::CancellationToken&>{cancelToken_};- }-- void setCancelToken(folly::CancellationToken&& cancelToken) noexcept {- if (!hasCancelTokenOverride_) {- cancelToken_ = std::move(cancelToken);- hasCancelTokenOverride_ = true;- }- }-- folly::AsyncStackFrame& getAsyncFrame() noexcept { return asyncFrame_; }- folly::Executor::KeepAlive<> getExecutor() const noexcept { return executor_; }@@ -206,10 +145,12 @@ // These getters exist so that `FinalAwaiter` can interact with wrapped // `TaskPromise`s, and not just `TaskPromiseBase` descendants. We use a // private tag to let `TaskWrapper` call them without becoming a `friend`.- auto& scopeExitRef(TaskPromisePrivate) { return scopeExit_; }- auto& continuationRef(TaskPromisePrivate) { return continuation_; }+ auto& scopeExitRef(TaskPromisePrivate /*unused*/) { return scopeExit_; }+ // FIXME: `result/coro.h` checks if this overload is callable to decide when+ // something is a task-promise. A second use-case would merit a concept.+ auto& continuationRef(TaskPromisePrivate /*unused*/) { return continuation_; } // Unlike `getExecutor()`, does not copy an atomic.- auto& executorRef(TaskPromisePrivate) { return executor_; }+ auto& executorRef(TaskPromisePrivate /*unused*/) { return executor_; } private: template <typename>@@ -219,32 +160,22 @@ friend class folly::coro::Task; friend coroutine_handle<ScopeExitTaskPromiseBase> tag_invoke(- cpo_t<co_attachScopeExit>,+ cpo_t<co_attachScopeExit> /*unused*/, TaskPromiseBase& p, coroutine_handle<ScopeExitTaskPromiseBase> scopeExit) noexcept { return std::exchange(p.scopeExit_, scopeExit); } - ExtendedCoroutineHandle continuation_;- folly::AsyncStackFrame asyncFrame_;- folly::Executor::KeepAlive<> executor_;- folly::CancellationToken cancelToken_;+ // From the base: continuation_, asyncFrame_, executor_, cancelToken_,+ // hasCancelTokenOverride_ coroutine_handle<ScopeExitTaskPromiseBase> scopeExit_;- bool hasCancelTokenOverride_ = false;-- protected:- enum class BypassExceptionThrowing : uint8_t {- INACTIVE,- ACTIVE,- REQUESTED,- } bypassExceptionThrowing_{BypassExceptionThrowing::INACTIVE}; }; // Separate from `TaskPromiseBase` so the compiler has less to specialize. template <typename Promise, typename T> class TaskPromiseCrtpBase : public TaskPromiseBase,- public ExtendedCoroutinePromise {+ public ExtendedCoroutinePromiseCrtp<Promise> { public: using StorageType = detail::lift_lvalue_reference_t<T>; @@ -266,31 +197,22 @@ return final_suspend(); } - using TaskPromiseBase::await_transform;+ using BasePromise<>::await_transform; - auto await_transform(co_safe_point_t) noexcept {- return do_safe_point(*this);+ auto await_transform(co_safe_point_t /*unused*/) noexcept {+ return do_safe_point<FinalAwaiter>(*this); } + // Unlike `getErrorHandleUncheckedImpl`, checks the type of `me`.+ static std::optional<ExtendedCoroutineHandle::ErrorHandle> getErrorHandleImpl(+ Promise& me, exception_wrapper& ex) {+ return getErrorHandleUncheckedImpl(me, ex);+ }+ protected: TaskPromiseCrtpBase() noexcept = default; ~TaskPromiseCrtpBase() = default; - std::pair<ExtendedCoroutineHandle, AsyncStackFrame*> getErrorHandle(- exception_wrapper& ex) final {- auto& me = *static_cast<Promise*>(this);- if (bypassExceptionThrowing_ == BypassExceptionThrowing::ACTIVE) {- auto finalAwaiter = yield_value(co_error(std::move(ex)));- DCHECK(!finalAwaiter.await_ready());- return {- finalAwaiter.await_suspend(- coroutine_handle<Promise>::from_promise(me)),- // finalAwaiter.await_suspend pops a frame- getAsyncFrame().getParentFrame()};- }- return {coroutine_handle<Promise>::from_promise(me), nullptr};- }- Try<StorageType> result_; }; @@ -354,14 +276,17 @@ // This CPO deliberately does NOT use `tag_invoke`, but rather reuses the // `co_withExecutor` name as the ADL implementation, just like `co_viaIfAsync`. // The reason is that `tag_invoke()` would plumb through `Awaitable&&` instead-// of `Awaitable`, but `must_await_immediately_v` types require by-value.+// of `Awaitable`, but `folly::ext::must_use_immediately_v` types require+// by-value. struct WithExecutorFunction { template <typename Awaitable> // Pass `awaitable` by-value, since `&&` would break immediate types auto operator()(Executor::KeepAlive<> executor, Awaitable awaitable) const FOLLY_DETAIL_FORWARD_BODY(co_withExecutor( std::move(executor),- mustAwaitImmediatelyUnsafeMover(std::move(awaitable))()))+ // NOLINTNEXTLINE(facebook-folly-coro-temporary-by-ref)+ folly::ext::must_use_immediately_unsafe_mover(+ std::move(awaitable))())) }; } // namespace adl @@ -380,7 +305,7 @@ /// /// More information on how to use this is available at folly::coro::Task. template <typename T>-class FOLLY_NODISCARD TaskWithExecutor {+class [[nodiscard]] TaskWithExecutor { using handle_t = coroutine_handle<detail::TaskPromise<T>>; using StorageType = typename detail::TaskPromise<T>::StorageType; @@ -482,7 +407,7 @@ F&& tryCallback, folly::CancellationToken cancelToken, void* returnAddress) && {- coro_.promise().setCancelToken(std::move(cancelToken));+ coro_.promise().setCancellationToken(std::move(cancelToken)); startImpl(std::move(*this), static_cast<F&&>(tryCallback)) .start(returnAddress); }@@ -492,12 +417,12 @@ F&& tryCallback, folly::CancellationToken cancelToken, void* returnAddress) && {- coro_.promise().setCancelToken(std::move(cancelToken));+ coro_.promise().setCancellationToken(std::move(cancelToken)); // If the task replaces the request context and reaches a suspension point, // it will not have a chance to restore the previous context before we // return, so we need to ensure it is restored. This simulates starting the // coroutine in an actual executor, which would wrap the task with a guard.- RequestContextScopeGuard contextScope{RequestContext::saveContext()};+ RequestContextSaverScopeGuard contextScope; startInlineImpl(std::move(*this), static_cast<F&&>(tryCallback)) .start(returnAddress); }@@ -634,7 +559,7 @@ template <typename Promise> FOLLY_NOINLINE coroutine_handle<> await_suspend(- coroutine_handle<Promise> continuation) {+ coroutine_handle<Promise> continuation) noexcept { DCHECK(coro_); auto& promise = coro_.promise(); DCHECK(!promise.continuation_);@@ -666,7 +591,8 @@ private: friend InlineTryAwaitable tag_invoke(- cpo_t<co_withAsyncStack>, InlineTryAwaitable&& awaitable) noexcept {+ cpo_t<co_withAsyncStack> /*unused*/,+ InlineTryAwaitable&& awaitable) noexcept { return std::move(awaitable); } @@ -699,22 +625,18 @@ friend TaskWithExecutor co_withCancellation( folly::CancellationToken cancelToken, TaskWithExecutor&& task) noexcept { DCHECK(task.coro_);- task.coro_.promise().setCancelToken(std::move(cancelToken));+ task.coro_.promise().setCancellationToken(std::move(cancelToken)); return std::move(task); } friend TaskWithExecutor tag_invoke(- cpo_t<co_withAsyncStack>, TaskWithExecutor&& task) noexcept {+ cpo_t<co_withAsyncStack> /*unused*/, TaskWithExecutor&& task) noexcept { return std::move(task); } - NoOpMover<TaskWithExecutor> getUnsafeMover(- ForMustAwaitImmediately) && noexcept {- return NoOpMover{std::move(*this)}; // Asserts `this` is nothrow-movable- }- using folly_private_task_without_executor_t = Task<T>;- // See comment in `Task`, or use `SafeTaskWithExecutor` instead.+ // See comment in `Task`, or use `safe_task_with_executor` instead.+ template <safe_alias> using folly_private_safe_alias_t = safe_alias_constant<safe_alias::unsafe>; private:@@ -728,7 +650,7 @@ // This macro makes it easier for `TaskWrapper.h` users to apply the correct // attributes for the wrapped `Task`s. #define FOLLY_CORO_TASK_ATTRS \- FOLLY_NODISCARD [[FOLLY_ATTR_CLANG_CORO_AWAIT_ELIDABLE]]+ [[nodiscard]] [[FOLLY_ATTR_CLANG_CORO_AWAIT_ELIDABLE]] /// Represents an allocated, but not-started coroutine, which is not yet /// been bound to an executor.@@ -805,7 +727,7 @@ /// Specify the executor that this task should execute on: /// co_withExecutor(executor, std::move(task)) //- /// @param executor An Executor::KeepAlive object, which can be implicity+ /// @param executor An Executor::KeepAlive object, which can be implicitly /// constructed from Executor* /// @returns a new TaskWithExecutor object, which represents the existing Task /// bound to an executor@@ -859,28 +781,29 @@ friend Task co_withCancellation( folly::CancellationToken cancelToken, Task&& task) noexcept { DCHECK(task.coro_);- task.coro_.promise().setCancelToken(std::move(cancelToken));+ task.coro_.promise().setCancellationToken(std::move(cancelToken)); return std::move(task); } template <typename F, typename... A, typename F_, typename... A_> friend Task tag_invoke(- tag_t<co_invoke_fn>, tag_t<Task, F, A...>, F_ f, A_... a) {- co_yield co_result(co_await co_awaitTry(- invoke(static_cast<F&&>(f), static_cast<A&&>(a)...)));- }-- NoOpMover<Task> getUnsafeMover(ForMustAwaitImmediately) && noexcept {- return NoOpMover{std::move(*this)}; // Asserts `this` is nothrow-movable+ tag_t<co_invoke_fn> /*unused*/,+ tag_t<Task, F, A...> /*unused*/,+ F_ f,+ A_... a) {+ co_yield co_result(+ co_await co_awaitTry(+ invoke(static_cast<F&&>(f), static_cast<A&&>(a)...))); } using PrivateAwaiterTypeForTests = Awaiter;- // Use `SafeTask` instead of `Task` to move tasks into other safe coro APIs.+ // Use `safe_task` instead of `Task` to move tasks into other safe coro APIs. // // User-facing stuff from `Task.h` can trivially include unsafe aliasing, the // `folly::coro` docs include hundreds of words of pitfalls. The intent here // is to catch people accidentally passing `Task`s into safer primitives, and // breaking their memory-safety guarantees.+ template <safe_alias> using folly_private_safe_alias_t = safe_alias_constant<safe_alias::unsafe>; private:@@ -957,7 +880,7 @@ // This overload needed as Awaiter is returned from co_viaIfAsync() which is // then passed into co_withAsyncStack(). friend Awaiter tag_invoke(- cpo_t<co_withAsyncStack>, Awaiter&& awaiter) noexcept {+ cpo_t<co_withAsyncStack> /*unused*/, Awaiter&& awaiter) noexcept { return std::move(awaiter); } @@ -981,7 +904,7 @@ co_return; } /// Same as makeTask(). See Unit-inline Task<void> makeTask(Unit) {+inline Task<void> makeTask(Unit /*unused*/) { co_return; } @@ -1004,6 +927,10 @@ template <typename Promise, typename T> inline Task<T> detail::TaskPromiseCrtpBase<Promise, T>::get_return_object() noexcept {+ // Watch out: When used with `TaskWrapper`, this relies on "practically safe"+ // UB wherein this handle is only valid because `TaskPromise` and the true+ // "wrapper promise" of the wrapper coro coincide in layout exactly.+ // Documented in `TaskPromiseWrapperBase::is_promise_type_punning_safe`. return Task<T>{ coroutine_handle<Promise>::from_promise(*static_cast<Promise*>(this))}; }
@@ -16,8 +16,8 @@ #pragma once -#include <folly/coro/AwaitImmediately.h> #include <folly/coro/Task.h>+#include <folly/lang/MustUseImmediately.h> /// `TaskWrapper.h` provides base classes for wrapping `folly::coro::Task` with /// custom functionality. These work by composition, which avoids the pitfalls@@ -44,43 +44,44 @@ /// /// ### WARNING: Do not blindly forward more APIs in `TaskWrapper.h`! ///-/// Several existing wrappers are immediately-awaitable (`AwaitImmediately.h`).-/// For those tasks (e.g. `NowTask`), API forwarding is risky:+/// Several existing wrappers are immediately-awaitable (see+/// `MustUseImmediately.h`). For those tasks (e.g. `now_task`), API+/// forwarding is risky: /// - Do NOT forward `semi()`, `start*()`, `unwrap()`, or other methods, or /// CPOs that take the awaitable by-reference. All of those make it /// trivial to accidentally break the immediately-awaitable invariant, and /// cause lifetime bugs. /// - When forwarding an API, use either a static method or CPO. Then, /// either ONLY take the awaitable by-value, or bifurcate the API on-/// `must_await_immediately_v<Awaitable>`, grep for examples.+/// `folly::ext::must_use_immediately_v<Awaitable>`, grep for examples. /// /// If you **have** to forward an unsafe API, here are some suggestions: /// - Only add them in your wrapper. /// - Add them via `UnsafeTaskWrapperCrtp` deriving from `TaskWrapperCrtp`. /// - Add boolean flags to the configuration struct, and gate the methods via /// `enable_if`. NB: You probably cannot gate these on `Derived` **not**-/// being `MustAwaitImmediately`, since CRTP bases see an incomplete type.+/// being must-use-immediately, since CRTP bases see an incomplete type. /// /// ### WARNING: Beware of object slicing in "unwrapping" APIs ///-/// Start by reading "A note on object slicing" in `AwaitImmediately.h`.+/// Start by reading "A note on object slicing" in `MustUseImmediately.h`. /// /// If your wrapper is adding new members, or customizing object lifecycle /// (dtor / copy / move / assignment), then you must:-/// - Write a custom `getUnsafeMover()`.+/// - Write a custom `unsafe_mover()` as per `lang/MustUseImmediately.h`. /// - Overload the protected `unsafeTask()` and `unsafeTaskWithExecutor()` to /// reduce slicing risk. /// - Take care not to slice down to the `Crtp` bases. /// /// ### How to implement a wrapper ///-/// First, read the WARNINGs above. Then, follow one of the "Tiny" examples-/// in `TaskWrapperTest.cpp`. The important things are:+/// First, read the WARNING sections above. Then, follow one of the "Tiny"+/// examples in `TaskWrapperTest.cpp`. The important things are: /// - Actually read the "object slicing" warning above! /// - In most cases, you'll need to both implement a task, and customize its /// `TaskWithExecutorT`. If you leave that as `coro::TaskWithExecutor`, /// some users will accidentally avoid your wrapper's effects.-/// - Tag `YourTaskWithExecutor` with `FOLLY_NODISCARD`.+/// - Tag `YourTaskWithExecutor` with `[[nodiscard]]`. /// - Tag `YourTask` with the `FOLLY_CORO_TASK_ATTRS` attribute. Caveat: /// This assumes that the coro's caller will outlive it. That is true for /// `Task`, and almost certainly true of all sensible wrapper types.@@ -121,6 +122,36 @@ template <typename T, typename WrapperTask, typename Promise> class TaskPromiseWrapperBase {+ private:+ // Detail of `is_promise_type_punning_safe`. Implementation notes:+ // - This needs a body because GCC doesn't want `d` referenced in a `->` type+ // signature.+ // - To use this with the cheaper-to-compile `FOLLY_DECLVAL`, which is+ // `nullptr`, this must be in an unevaluated context, since patently-null+ // static casts are special in that they discard offsets. So, the below+ // equality would always be true during constant evaluation.+ template <typename Me>+ static consteval auto promise_at_offset0(Me me) {+ return std::bool_constant<+ static_cast<const void*>(&me) ==+ static_cast<const void*>(&me.promise_)>{};+ }++ // CRITICAL SAFETY CHECK: `&promise_` must be the same as `this`, and both+ // objects must have the same size. This is required since some promise+ // operations (notably `get_return_object()` need to obtain a+ // `coroutine_handle<Promise>` to allow the wrapped coro not to know about+ // the wrapper. At the same time, the actual promise is+ // `TaskPromiseWrapperBase`. Punning promises & handles in this way is+ // technically UB, but it's practically safe so long as the layouts of+ // `Promise` and `TaskPromiseWrapperBase` are identical, which is what we+ // verify here.+ static consteval bool is_promise_type_punning_safe() {+ return require_sizeof<Promise> == require_sizeof<TaskPromiseWrapperBase> &&+ decltype(promise_at_offset0(+ FOLLY_DECLVAL(TaskPromiseWrapperBase)))::value;+ }+ protected: static_assert( is_task_or_wrapper_v<WrapperTask, T>,@@ -138,6 +169,11 @@ using TaskWrapperInnerPromise = Promise; WrapperTask get_return_object() noexcept {+ // CRITICAL: This assert justifies why it is practically safe to rely on+ // the `from_promise` UB in `TaskPromiseCrtpBase::get_return_object`.+ //+ // PS The assert isn't at class scope, since the type would be incomplete.+ static_assert(is_promise_type_punning_safe()); return WrapperTask{promise_.get_return_object()}; } @@ -153,16 +189,16 @@ template < typename Awaitable,- std::enable_if_t<!must_await_immediately_v<Awaitable>, int> = 0>+ std::enable_if_t<!folly::ext::must_use_immediately_v<Awaitable>, int> = 0> auto await_transform(Awaitable&& what) { return promise_.await_transform(std::forward<Awaitable>(what)); } template < typename Awaitable,- std::enable_if_t<must_await_immediately_v<Awaitable>, int> = 0>+ std::enable_if_t<folly::ext::must_use_immediately_v<Awaitable>, int> = 0> auto await_transform(Awaitable what) { return promise_.await_transform(- mustAwaitImmediatelyUnsafeMover(std::move(what))());+ folly::ext::must_use_immediately_unsafe_mover(std::move(what))()); } auto yield_value(auto&& v)@@ -185,6 +221,16 @@ auto& executorRef(TaskPromisePrivate tag) { return promise_.executorRef(tag); }++ // These next two definitions forward `getErrorHandle` behavior to the+ // innermost promise object. See `ExtendedCoroutineHandle` for docs.++ using use_extended_handle_concept = ExtendedCoroutineHandle::PrivateTag;++ static ExtendedCoroutineHandle::PromiseBase* getPromiseBase(+ ExtendedCoroutineHandle::PrivateTag priv, TaskPromiseWrapperBase* me) {+ return Promise::getPromiseBase(priv, &me->promise_);+ } }; template <typename T, typename WrapperTask, typename Promise>@@ -197,6 +243,9 @@ public: template <typename U = T> // see "`co_return` with implicit ctor" test auto return_value(U&& value) {+ static_assert( // See `is_promise_type_punning_safe` for rationale+ require_sizeof<TaskPromiseWrapper> ==+ require_sizeof<TaskPromiseWrapperBase<T, WrapperTask, Promise>>); return this->promise_.return_value(std::forward<U>(value)); } };@@ -209,7 +258,12 @@ ~TaskPromiseWrapper() = default; public:- void return_void() noexcept { this->promise_.return_void(); }+ void return_void() noexcept {+ static_assert( // See `is_promise_type_punning_safe` for rationale+ require_sizeof<TaskPromiseWrapper> ==+ require_sizeof<TaskPromiseWrapperBase<void, WrapperTask, Promise>>);+ this->promise_.return_void();+ } }; // Mixin for TaskWrapper.h configs for `Task` & `TaskWithExecutor` types@@ -250,30 +304,23 @@ Executor::KeepAlive<> executor, Derived tw) noexcept { return Cfg::wrapAwaitable(co_viaIfAsync( std::move(executor),- mustAwaitImmediatelyUnsafeMover(std::move(tw).unwrapTask())()));+ folly::ext::must_use_immediately_unsafe_mover(+ std::move(tw).unwrapTask())())); } // No `cpo_t<co_withAsyncStack>` since a "Task" is not an awaitable. - auto getUnsafeMover(ForMustAwaitImmediately p) && noexcept {- // See "A note on object slicing" above `mustAwaitImmediatelyUnsafeMover`- static_assert(sizeof(Derived) == sizeof(typename Cfg::InnerTaskT));- static_assert( // More `noexcept` tests in `MustAwaitImmediatelyUnsafeMover`- noexcept(std::move(*this).unwrapTask().getUnsafeMover(p)));- return MustAwaitImmediatelyUnsafeMover{- (Derived*)nullptr, std::move(*this).unwrapTask().getUnsafeMover(p)};- }- using folly_private_task_wrapper_inner_t = typename Cfg::InnerTaskT; using folly_private_task_wrapper_crtp_base = TaskWrapperCrtp; // Wrappers can override these as-needed- using folly_private_must_await_immediately_t =- must_await_immediately_t<typename Cfg::InnerTaskT>;- using folly_private_noexcept_awaitable_t =- noexcept_awaitable_t<typename Cfg::InnerTaskT>;+ using folly_must_use_immediately_t =+ ext::must_use_immediately_t<typename Cfg::InnerTaskT>;+ using folly_private_value_only_awaitable_t =+ value_only_awaitable_t<typename Cfg::InnerTaskT>;+ template <safe_alias Default> using folly_private_safe_alias_t =- safe_alias_of<folly_private_task_wrapper_inner_t>;+ safe_alias_of<folly_private_task_wrapper_inner_t, Default>; private: using Inner = folly_private_task_wrapper_inner_t;@@ -281,31 +328,61 @@ detail::is_task_or_wrapper_v<Inner, typename Cfg::ValueT>, "*TaskWrapper must wrap a sequence of wrappers ending in Task<T>"); - Inner task_;+ Inner inner_; protected: template <typename, typename, typename> // can construct friend class ::folly::coro::detail::TaskPromiseWrapperBase;- friend class MustAwaitImmediatelyUnsafeMover< // can construct- Derived,- detail::unsafe_mover_for_must_await_immediately_t<Inner>>; - explicit TaskWrapperCrtp(Inner t)- // `mustAwaitImmediatelyUnsafeMover` has more `noexcept` assertions.- noexcept(noexcept(Inner{FOLLY_DECLVAL(Inner)}))- : task_(mustAwaitImmediatelyUnsafeMover(std::move(t))()) {+ explicit TaskWrapperCrtp(Inner t) noexcept+ // NOLINTNEXTLINE(facebook-folly-coro-temporary-by-ref)+ : inner_(folly::ext::must_use_immediately_unsafe_mover(std::move(t))()) { static_assert(- must_await_immediately_v<Derived> ||- !must_await_immediately_v<typename Cfg::TaskWithExecutorT>,- "`TaskWithExecutorT` must `AddMustAwaitImmediately` because the inner "+ folly::ext::must_use_immediately_v<Derived> ||+ !folly::ext::must_use_immediately_v<+ typename Cfg::TaskWithExecutorT>,+ "`TaskWithExecutorT` must `ext::wrap_must_use_immediately_t` because the inner " "task did"); } - // See "A note on object slicing" above `mustAwaitImmediatelyUnsafeMover`+ // See "A note on object slicing" in `MustUseImmediately.h` Inner unwrapTask() && noexcept { static_assert(sizeof(Inner) == sizeof(Derived));- return mustAwaitImmediatelyUnsafeMover(std::move(task_))();+ return folly::ext::must_use_immediately_unsafe_mover(std::move(inner_))(); }++ private:+ template <typename T>+ using my_curried_mover = folly::ext::curried_unsafe_mover_t<+ T,+ decltype(folly::ext::must_use_immediately_unsafe_mover(+ FOLLY_DECLVAL(Inner)))>;++ public:+ template <+ typename Me, // not a forwarding ref, see SFINAE+ // This check guards against misuse (+ fails on lvalue refs)+ // See `wrap_must_use_immediately_t::unsafe_mover` for more context+ std::enable_if_t<std::is_base_of_v<TaskWrapperCrtp, Me>, int> = 0>+ static my_curried_mover<Me> unsafe_mover(+ folly::ext::must_use_immediately_private_t /*unused*/, Me&& me) noexcept {+ return folly::ext::curried_unsafe_mover_from_bases_and_members<+ TaskWrapperCrtp>(+ folly::tag</*no bases*/>,+ folly::vtag<&TaskWrapperCrtp::inner_>,+ static_cast<Me&&>(me));+ }+ template <+ typename DerivedFromMe,+ // Matches the SFINAE logic in our `unsafe_mover`+ std::enable_if_t<std::is_base_of_v<TaskWrapperCrtp, DerivedFromMe>, int> =+ 0>+ explicit TaskWrapperCrtp(+ folly::ext::curried_unsafe_mover_private_t /*unused*/,+ my_curried_mover<DerivedFromMe>&& mover)+ // `must_use_immediately_unsafe_mover` has more `noexcept` assertions+ noexcept(noexcept(Inner{std::move(mover.template get<0>())()}))+ : inner_{std::move(mover.template get<0>())()} {} }; // IMPORTANT: Read "Do not blindly forward more APIs" in the file docblock. In@@ -318,23 +395,18 @@ Inner inner_; protected:- friend class MustAwaitImmediatelyUnsafeMover< // can construct- Derived,- detail::unsafe_mover_for_must_await_immediately_t<Inner>>;-- // See "A note on object slicing" above `mustAwaitImmediatelyUnsafeMover`+ // See "A note on object slicing" in `MustUseImmediately.h` Inner unwrapTaskWithExecutor() && noexcept { static_assert(sizeof(Inner) == sizeof(Derived));- return mustAwaitImmediatelyUnsafeMover(std::move(inner_))();+ return folly::ext::must_use_immediately_unsafe_mover(std::move(inner_))(); } // Our task can construct us, and that logic lives in the CRTP base friend typename Cfg::WrapperTaskT::folly_private_task_wrapper_crtp_base; - explicit TaskWithExecutorWrapperCrtp(Inner t)- // `mustAwaitImmediatelyUnsafeMover` has more `noexcept` assertions.- noexcept(noexcept(Inner{FOLLY_DECLVAL(Inner)}))- : inner_(mustAwaitImmediatelyUnsafeMover(std::move(t))()) {}+ explicit TaskWithExecutorWrapperCrtp(Inner t) noexcept(noexcept(Inner{+ FOLLY_DECLVAL(Inner)}))+ : inner_(folly::ext::must_use_immediately_unsafe_mover(std::move(t))()) {} public: // This is a **deliberately undefined** declaration. It is provided so that@@ -364,7 +436,8 @@ CancellationToken cancelToken, Derived twe) noexcept { return Derived{co_withCancellation( std::move(cancelToken),- mustAwaitImmediatelyUnsafeMover(std::move(twe.inner_))())};+ folly::ext::must_use_immediately_unsafe_mover(+ std::move(twe.inner_))())}; } // Pass `twe` by-value, since `&&` would break immediately-awaitable types@@ -373,7 +446,8 @@ Executor::KeepAlive<> executor, Derived twe) noexcept { return Cfg::wrapAwaitable(co_viaIfAsync( std::move(executor),- mustAwaitImmediatelyUnsafeMover(std::move(twe.inner_))()));+ folly::ext::must_use_immediately_unsafe_mover(+ std::move(twe.inner_))())); } // `AsyncScope` requires an awaitable with an executor already attached, and@@ -381,13 +455,13 @@ // we still need to wrap the awaitable on that code path. // // NB: Passing by-&& here looks like it could compromise the safety of- // immediately-awaitable coros (`NowTask`, `NowTaskWithExecutor`). With+ // immediately-awaitable coros (`now_task`, `now_task_with_executor`). With // by-value, `BlockingWaitTest.AwaitNowTaskWithExecutor` would not build. // // Supporting pass-by-value would require fixing a LOT of plumbing. // - `WithAsyncStack.h` calls `is_tag_invocable_v`, which would fail on- // `NowTaskWithExecutor` if this is by-value, since the implementation of- // `is_tag_invocable_v` presents all args by-&&.+ // `now_task_with_executor` if this is by-value, since the implementation+ // of `is_tag_invocable_v` presents all args by-&&. // - `CommutativeWrapperAwaitable` and `StackAwareViaIfAsyncAwaiter`, // among others, also assume that `co_withAsyncStack` takes by-ref. //@@ -396,24 +470,52 @@ // never be called in user code. Internal usage in `folly/coro` looks // overall immediately-awaitable-safe -- and the best safeguard for any // particular scenario is to test, see e.g. `NowTaskTest.blockingWait`.- friend auto tag_invoke(cpo_t<co_withAsyncStack>, Derived&& twe) noexcept(+ friend auto+ tag_invoke(cpo_t<co_withAsyncStack> /*unused*/, Derived&& twe) noexcept( noexcept(co_withAsyncStack(FOLLY_DECLVAL(Inner)))) { return Cfg::wrapAwaitable(co_withAsyncStack(std::move(twe.inner_))); } - auto getUnsafeMover(ForMustAwaitImmediately p) && noexcept {- // See "A note on object slicing" above `mustAwaitImmediatelyUnsafeMover`- static_assert(sizeof(Derived) == sizeof(Inner));- static_assert( // More `noexcept` tests in `MustAwaitImmediatelyUnsafeMover`- noexcept(std::move(inner_).getUnsafeMover(p)));- return MustAwaitImmediatelyUnsafeMover{- (Derived*)nullptr, std::move(inner_).getUnsafeMover(p)};- }-- using folly_private_must_await_immediately_t =- must_await_immediately_t<Inner>;+ using folly_must_use_immediately_t = ext::must_use_immediately_t<Inner>; using folly_private_task_without_executor_t = typename Cfg::WrapperTaskT;- using folly_private_safe_alias_t = safe_alias_of<Inner>;+ template <safe_alias Default>+ using folly_private_safe_alias_t = safe_alias_of<Inner, Default>;++ private:+ template <typename T>+ using my_curried_mover = folly::ext::curried_unsafe_mover_t<+ T,+ decltype(folly::ext::must_use_immediately_unsafe_mover(+ FOLLY_DECLVAL(Inner)))>;++ public:+ template <+ typename Me, // not a forwarding ref, see SFINAE+ // This check guards against misuse (+ fails on lvalue refs)+ // See `wrap_must_use_immediately_t::unsafe_mover` for more context+ std::enable_if_t<+ std::is_base_of_v<TaskWithExecutorWrapperCrtp, Me>,+ int> = 0>+ static my_curried_mover<Me> unsafe_mover(+ folly::ext::must_use_immediately_private_t /*unused*/, Me&& me) noexcept {+ return folly::ext::curried_unsafe_mover_from_bases_and_members<+ TaskWithExecutorWrapperCrtp>(+ folly::tag</*no bases*/>,+ folly::vtag<&TaskWithExecutorWrapperCrtp::inner_>,+ static_cast<Me&&>(me));+ }+ template <+ typename DerivedFromMe,+ // Matches the SFINAE logic in our `unsafe_mover`+ std::enable_if_t<+ std::is_base_of_v<TaskWithExecutorWrapperCrtp, DerivedFromMe>,+ int> = 0>+ explicit TaskWithExecutorWrapperCrtp(+ folly::ext::curried_unsafe_mover_private_t /*unused*/,+ my_curried_mover<DerivedFromMe>&& mover)+ // `must_use_immediately_unsafe_mover` has more `noexcept` assertions+ noexcept(noexcept(Inner{std::move(mover.template get<0>())()}))+ : inner_{std::move(mover.template get<0>())()} {} }; } // namespace folly::coro
@@ -79,8 +79,8 @@ exception_wrapper error; try {- auto resultTry =- co_await folly::coro::co_awaitTry(folly::coro::co_withCancellation(+ auto resultTry = co_await folly::coro::co_awaitTry(+ folly::coro::co_withCancellation( cancelSource.getToken(), std::move(semiFn)())); cancelCallback.reset();@@ -132,7 +132,7 @@ typename detail::TimeoutTask<SemiAwaitable, TimekeeperPtr> timeout( SemiAwaitable semiAwaitable, Duration timeoutDuration, TimekeeperPtr tk) { return detail::timeoutImpl<SemiAwaitable, Duration, /*discard=*/true>(- mustAwaitImmediatelyUnsafeMover(std::move(semiAwaitable)),+ folly::ext::must_use_immediately_unsafe_mover(std::move(semiAwaitable)), timeoutDuration, std::move(tk)); }@@ -141,7 +141,7 @@ typename detail::TimeoutTask<SemiAwaitable, TimekeeperPtr> timeoutNoDiscard( SemiAwaitable semiAwaitable, Duration timeoutDuration, TimekeeperPtr tk) { return detail::timeoutImpl<SemiAwaitable, Duration, /*discard=*/false>(- mustAwaitImmediatelyUnsafeMover(std::move(semiAwaitable)),+ folly::ext::must_use_immediately_unsafe_mover(std::move(semiAwaitable)), timeoutDuration, std::move(tk)); }
@@ -21,9 +21,9 @@ #include <folly/coro/Traits.h> #include <folly/coro/detail/PickTaskWrapper.h> #include <folly/futures/Future.h>-// `timeout(coroFutureInt())` makes a `SafeTask`+// `timeout(coroFutureInt())` makes a `safe_task` #include <folly/coro/safe/SafeTask.h>-// `timeout(memberTask())` makes a `NowTask`+// `timeout(memberTask())` makes a `now_task` #include <folly/coro/safe/NowTask.h> #if FOLLY_HAS_COROUTINES@@ -31,14 +31,16 @@ namespace folly::coro { namespace detail {-// This doesn't try to apply `AsNoexcept` to the output, since `timeout` is+// This doesn't try to apply `value_or_fatal` to the output, since `timeout` is // expected to throw, and `timeoutNoDiscard()` may either complete with a // stopped state, or with an error. template <typename SemiAwaitable, typename TimekeeperPtr>-using TimeoutTask = PickTaskWrapper<+using TimeoutTask = pick_task_wrapper< typename semi_await_try_result_t<SemiAwaitable>::element_type,- std::min(safe_alias_of_v<TimekeeperPtr>, safe_alias_of_v<SemiAwaitable>),- must_await_immediately_v<SemiAwaitable>>;+ std::min(+ lenient_safe_alias_of_v<TimekeeperPtr>,+ lenient_safe_alias_of_v<SemiAwaitable>),+ folly::ext::must_use_immediately_v<SemiAwaitable>>; } // namespace detail /// Returns a Task that, when started, starts a timer of duration@@ -65,7 +67,7 @@ typename SemiAwaitable, typename Duration, // Templated so we can take safe pointers like `capture<Timekeeper&>` from- // `folly/coro/safe`, and return a `SafeTask`.+ // `folly/coro/safe`, and return a `safe_task`. typename TimekeeperPtr = std::nullptr_t> typename detail::TimeoutTask<SemiAwaitable, TimekeeperPtr> timeout( SemiAwaitable semiAwaitable,
@@ -28,7 +28,12 @@ // Wrapper around folly::UnboundedQueue with async wait -template <typename T, bool SingleProducer = false, bool SingleConsumer = false>+template <+ typename T,+ bool SingleProducer = false,+ bool SingleConsumer = false,+ bool MayBlock = false,+ size_t LgSegmentSize = 8> class UnboundedQueue { public: template <typename U = T>@@ -87,7 +92,13 @@ size_t size() const { return queue_.size(); } private:- folly::UnboundedQueue<T, SingleProducer, SingleConsumer, false> queue_;+ folly::UnboundedQueue< //+ T,+ SingleProducer,+ SingleConsumer,+ MayBlock,+ LgSegmentSize>+ queue_; folly::fibers::Semaphore sem_{0}; };
@@ -0,0 +1,235 @@+/*+ * Copyright (c) Meta Platforms, Inc. and affiliates.+ *+ * Licensed under the Apache License, Version 2.0 (the "License");+ * you may not use this file except in compliance with the License.+ * You may obtain a copy of the License at+ *+ * http://www.apache.org/licenses/LICENSE-2.0+ *+ * Unless required by applicable law or agreed to in writing, software+ * distributed under the License is distributed on an "AS IS" BASIS,+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.+ * See the License for the specific language governing permissions and+ * limitations under the License.+ */++#pragma once++#include <folly/coro/ViaIfAsync.h>+#include <folly/result/try.h>++#if FOLLY_HAS_RESULT++namespace folly::coro {++/// `value_or_error_or_stopped` is the `result<T>` analog of the older+/// `co_awaitTry`.+///+/// In a `folly::coro` async coroutine, use `value_or_error_or_stopped` like so:+///+/// result<int> res = co_await value_or_error_or_stopped(taskReturningInt());+/// if (auto ex = get_exception<MyError>(res)) {+/// /* handle ex */+/// } else {+/// sum += co_await co_ready(res); // efficiently propagate unhandled error+/// }+///+/// Contrast that with related async coro vocabulary:+/// - `co_yield co_result(r)` from `Result.h` -- propagate `result<T>` or+/// `Try<T>` to the awaiter of the current coro.+/// - `auto& v = co_await co_ready(r)` from `Ready.h` -- given a `result<T>`,+/// unpack the value, or propagate any error to our awaiter.+///+/// The purpose of `value_or_error_or_stopped` is to handle errors from a child+/// task via `result<T>`, rather than through `try {} catch {}`. Some reasons+/// to do so:+/// - Your error-handling APIs (logging, retry, etc) use `result<T>`.+/// - You wish to avoid the ~microsecond cost of thrown exceptions,+/// applicable only when your error path is hot, and the child uses+/// `co_yield` instead of `throw` to propagate exceptions.++namespace detail {++template <typename Awaiter>+using detect_await_resume_result =+ decltype(FOLLY_DECLVAL(Awaiter).await_resume_result());++template <typename Awaiter>+constexpr bool is_awaiter_result =+ is_detected_v<detect_await_resume_result, Awaiter>;++template <typename Awaitable>+constexpr bool is_awaitable_result =+ is_awaiter_result<awaiter_type_t<Awaitable>>;++// On the happy path, this uses the dedicated `await_resume_result()` protocol,+// if it's supported by the awaiter.+//+// As fallback, this reuses the `await_resume_try()` machinery in the hope that+// the compiler will be able to optimize away the `Try` -> `result` conversion.+//+// The reasons to support the dedicated protocol are (1) better semantics, and+// (2) a data flow that's easier for the compiler to optimize. Specifically:+//+// - `await_resume_result()` cleanly handles `Task<V&>`, whereas `Try`+// doesn't support storing references, and the caller of `co_awaitTry` has+// to deal with `Try<std::reference_wrapper<V>>`. See the test in+// `TaskOfLvalueReferenceAsTry`.+//+// - `await_resume_result()` implementations can explicitly avoid the "empty+// `Try`" pitfall, which is something that gets converted to a+// `UsingUninitializedTry` error by the `try_to_result()` fallback.+//+// - Falling back to `await_resume_try()` can incur an extra move-copy, which+// may not always optimize away.+template <typename Awaitable>+class ResultAwaiter : public ValueOnlyAwaiterBase<Awaitable> {+ private:+ static_assert(is_awaitable_try<Awaitable> || is_awaitable_result<Awaitable>);++ using Base = ValueOnlyAwaiterBase<Awaitable>;++ public:+ explicit ResultAwaiter(Awaitable&& awaitable)+ : Base(static_cast<Awaitable&&>(awaitable)) {}++ // Preferred `await_resume()`: use `await_resume_result()` when available+ template <+ typename Awaiter2 = typename Base::Awaiter,+ typename Result =+ decltype(FOLLY_DECLVAL(Awaiter2&).await_resume_result())>+ Result await_resume() noexcept(+ noexcept(this->awaiter_.await_resume_result())) {+ return this->awaiter_.await_resume_result();+ }++ // Fallback `await_resume()`: use `await_resume_try()` otherwise+ template <+ typename Awaiter2 = typename Base::Awaiter,+ typename Result =+ decltype(try_to_result(FOLLY_DECLVAL(Awaiter2&).await_resume_try()))>+ Result await_resume() noexcept(+ noexcept(try_to_result(this->awaiter_.await_resume_try())))+ requires(!is_awaitable_result<Awaitable>) // Fallback logic+ {+ return try_to_result(this->awaiter_.await_resume_try());+ }+};++template <typename T>+class [[FOLLY_ATTR_CLANG_CORO_AWAIT_ELIDABLE]] ValueOrErrorOrStopped+ : public CommutativeWrapperAwaitable<ValueOrErrorOrStopped, T> {+ public:+ using CommutativeWrapperAwaitable<ValueOrErrorOrStopped, T>::+ CommutativeWrapperAwaitable;++ template <+ typename Self,+ std::enable_if_t<+ std::is_same_v<remove_cvref_t<Self>, ValueOrErrorOrStopped>,+ int> = 0,+ typename T2 = like_t<Self, T>,+ std::enable_if_t<is_awaitable_v<T2>, int> = 0>+ friend ResultAwaiter<T2> operator co_await(Self&& self) {+ return ResultAwaiter<T2>{static_cast<Self&&>(self).inner_};+ }++ using folly_private_value_only_awaitable_t = std::true_type;+};++// The awaitable backing `ValueOrError`, see that doc for why it's separate.+//+// NB: Besides `folly_private_value_only_awaitable_t`, this is identical to+// `ValueOrErrorOrStopped`, but the way the `CommutativeWrapperAwaitable`+// template is set up, it's a lot of code to parameterize it.+//+// IMPORTANT: this will never return "stopped" since `BasePromise` sets up+// `OperationCancelled` to unwind directly to the parent.+template <typename T>+class [[FOLLY_ATTR_CLANG_CORO_AWAIT_ELIDABLE]]+ValueOrErrorImpl : public CommutativeWrapperAwaitable<ValueOrErrorImpl, T> {+ public:+ using CommutativeWrapperAwaitable<ValueOrErrorImpl, T>::+ CommutativeWrapperAwaitable;++ template <+ typename Self,+ std::enable_if_t<+ std::is_same_v<remove_cvref_t<Self>, ValueOrErrorImpl>,+ int> = 0,+ typename T2 = like_t<Self, T>,+ std::enable_if_t<is_awaitable_v<T2>, int> = 0>+ friend ResultAwaiter<T2> operator co_await(Self&& self) {+ return ResultAwaiter<T2>{static_cast<Self&&>(self).inner_};+ }++ // Future: Cannot mark `folly_private_value_only_awaitable_t` since it+ // completes with "stopped" which may throw in contexts like `blocking_wait`.+ // Once the `awaitable_completions_v` refactor is complete, this should+ // remove the "error" completion from the underlying awaitable.+};++// Similarly to NothrowAwaitable, this deliberately omits `co_await` and relies+// on `BasePromise::await_transform`. The goal is to prevent its use in+// contexts that cannot provide nothrow-propagation semantics for stopped+// coros. Gating on `BasePromise` provides a tightly controlled allowlist of+// where this can be awaited, and how.+//+// For a specific example, consider `blocking_wait(value_or_error(...))` --+// this should either not compile, or throw on `OperationCancelled`. The+// latter "sometimes throwing" semantics would be a surprising footgun, so we+// instead require the obvious contract of `value_or_error_or_stopped()` here.+template <typename T>+class [[FOLLY_ATTR_CLANG_CORO_AWAIT_ELIDABLE]]+ValueOrError : public CommutativeWrapperAwaitable<ValueOrError, T> {+ public:+ using CommutativeWrapperAwaitable<ValueOrError, T>::+ CommutativeWrapperAwaitable;++ protected:+ template <typename>+ friend class BasePromise;+ ValueOrErrorImpl<T> toValueOrErrorImpl() && {+ return ValueOrErrorImpl<T>{folly::ext::must_use_immediately_unsafe_mover(+ std::move(this->inner_))()};+ }++ // Future: See `ValueOrErrorImpl` regarding+ // `folly_private_value_only_awaitable_t` and `awaitable_completions_v`.+};++} // namespace detail++// BEFORE CHANGING: If you need an `Awaitable&&` overload, you must bifurcate+// these APIs on `must_use_immediately_v`, see `co_awaitTry` for an example.++/// When awaited, returns a `result<T>` in a value or an error state, but never+/// `has_stopped()` (aka `OperationCancelled`). If `awaitable` reports as+/// "stopped", the awaiting coroutine will be promptly torn down, **without+/// throwing**, and its parent will in turn receive a "stopped" completion.+///+/// BE CAREFUL -- This is `co_nothrow` semantics, but **only** for+/// cancellation. -- if your coro requires async cleanup, such as awaiting an+/// async scope or other background work, then you **must** use a safe async+/// RAII pattern, such `async_closure` if applicable (or `co_scope_exit`, but+/// beware of its lifetime risks).+template <typename Awaitable>+detail::ValueOrError<Awaitable> value_or_error(+ [[FOLLY_ATTR_CLANG_CORO_AWAIT_ELIDABLE_ARGUMENT]] Awaitable awaitable) {+ return detail::ValueOrError<Awaitable>{+ folly::ext::must_use_immediately_unsafe_mover(std::move(awaitable))()};+}++/// When awaited, returns `result<T>` just like `value_or_error()`, but also+/// captures "stopped" completions, so you **must** test for `has_stopped()`.+template <typename Awaitable>+detail::ValueOrErrorOrStopped<Awaitable> value_or_error_or_stopped(+ [[FOLLY_ATTR_CLANG_CORO_AWAIT_ELIDABLE_ARGUMENT]] Awaitable awaitable) {+ return detail::ValueOrErrorOrStopped<Awaitable>{+ folly::ext::must_use_immediately_unsafe_mover(std::move(awaitable))()};+}++} // namespace folly::coro++#endif
@@ -0,0 +1,337 @@+/*+ * Copyright (c) Meta Platforms, Inc. and affiliates.+ *+ * Licensed under the Apache License, Version 2.0 (the "License");+ * you may not use this file except in compliance with the License.+ * You may obtain a copy of the License at+ *+ * http://www.apache.org/licenses/LICENSE-2.0+ *+ * Unless required by applicable law or agreed to in writing, software+ * distributed under the License is distributed on an "AS IS" BASIS,+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.+ * See the License for the specific language governing permissions and+ * limitations under the License.+ */++#pragma once++#include <folly/Unit.h>+#include <folly/coro/TaskWrapper.h>+#include <folly/coro/ViaIfAsync.h>+#include <folly/lang/Assume.h>+#include <folly/result/result.h>+#include <folly/result/try.h>+#include <folly/result/value_only_result.h>++#if FOLLY_HAS_COROUTINES++/// `value_or_fatal<Task<T>, Policy>` wraps a task to guarantee value-only+/// completion. Use this only with APIs that require it -- like `co_cleanup()`+/// async RAII, or async scopes. If your code compiles without it, skip it!+///+/// You must specify a policy at least for "stopped" -- from C++26 onward,+/// cancellation is not an error: https://wg21.link/p1677+///+/// Policy | Stopped | Error+/// ----------------------------------|---------|------+/// on_stopped_void | void | fatal+/// on_stopped<V> | V | fatal+/// on_stopped_and_error<V> | V | V+/// on_stopped_and_error<V, W> | V | W+/// on_stopped_and_error<will_fatal> | fatal | fatal+///+/// ## Why not just `noexcept`?+///+/// A `noexcept` coroutine only fatals on errors during frame construction --+/// awaiting it can still throw. `value_or_fatal<>` makes the await not throw.+///+/// ## This is probably not for you+///+/// - Not related to `co_nothrow`, which efficiently propagates exceptions to+/// the parent, without letting the awaiting task handle them.+/// - Not a general "avoid exceptions" tool -- the wrapper adds build cost,+/// frame allocation can still throw, and `std::terminate` offramps can+/// pessimize vs normal exception propagation.+///+/// ## Custom policies+///+/// struct my_policy_t {+/// static int value_only_default(non_value_result&& nv) noexcept {+/// LOG(ERROR) << (nv.has_stopped() ? "stopped" : "error");+/// return 0;+/// }+/// };+/// inline constexpr my_policy_t my_policy{};+/// value_or_fatal<Task<int>, my_policy> foo() { ... }++namespace folly::coro {++// Tag for "terminate on this completion"+struct will_fatal_t {};+inline constexpr will_fatal_t will_fatal{};++/// Primary policy struct: specify behavior for stopped (cancellation) and+/// error. Use via the variable templates below, not directly.+template <auto Stopped, auto Error>+class on_stopped_and_error_t {+ private:+ template <auto V, typename RetT>+ static constexpr RetT value_or_terminate(non_value_result&& nv) noexcept {+ if constexpr (std::is_same_v<decltype(V), will_fatal_t>) {+ nv.throw_exception(); // Better error than `std::terminate()`+ folly::assume_unreachable();+ } else {+ return V;+ }+ }++ public:+ template <+ typename RetT = conditional_t<+ std::is_same_v<decltype(Stopped), will_fatal_t>,+ decltype(Error),+ decltype(Stopped)>>+ static constexpr RetT value_only_default(non_value_result&& nv) noexcept {+ return nv.has_stopped()+ ? value_or_terminate<Stopped, RetT>(std::move(nv))+ : value_or_terminate<Error, RetT>(std::move(nv));+ }+};++template <auto Stopped, auto Error = Stopped>+inline constexpr auto on_stopped_and_error =+ on_stopped_and_error_t<Stopped, Error>{};++/// Convenience: stopped returns V, error terminates (fatal).+template <auto V>+inline constexpr auto on_stopped = on_stopped_and_error<V, will_fatal>;++/// Convenience for void tasks: stopped returns unit, error terminates.+inline constexpr auto on_stopped_void = on_stopped<unit>;++namespace detail {++// Detect if a type is a TaskWithExecutor-like type (has an attached executor).+template <typename T>+inline constexpr bool is_task_with_executor_v = requires {+ typename T::folly_private_task_without_executor_t;+};++template <typename Awaitable, auto Policy>+class ValueOrFatalAwaiter : public ValueOnlyAwaiterBase<Awaitable> {+ private:+ using Base = ValueOnlyAwaiterBase<Awaitable>;+ using RetT = decltype(FOLLY_DECLVAL(typename Base::Awaiter).await_resume());++ public:+ explicit ValueOrFatalAwaiter(Awaitable&& awaitable)+ : Base(static_cast<Awaitable&&>(awaitable)) {}++ // Returns `value_only_result<T>` for composition with `await_resume_result()`+ // protocol. Implicitly converts to `result<T>` for callers expecting that.+ value_only_result<RetT> await_resume_result() noexcept {+ // Get result from awaiter using the best available protocol+ auto res = [&]() -> result<RetT> {+ if constexpr (is_awaiter_result<typename Base::Awaiter>) {+ // Catch missed optimization: `awaiter_` returning `value_only_result`+ // would needlessly convert `value_only_result` to `result`, then back.+ static_assert(+ !is_instantiation_of_v<+ value_only_result,+ std::remove_cvref_t<+ decltype(this->awaiter_.await_resume_result())>>);+ return this->awaiter_.await_resume_result();+ } else if constexpr (is_awaiter_try<typename Base::Awaiter>) {+ return try_to_result(this->awaiter_.await_resume_try());+ } else {+ try {+ if constexpr (std::is_void_v<RetT>) {+ this->awaiter_.await_resume();+ return {};+ } else {+ return this->awaiter_.await_resume();+ }+ } catch (...) { // Policy may handle both "stopped" and "error" below+ return non_value_result::from_current_exception();+ }+ }+ }();++ // Handle result uniformly+ if (res.has_value()) {+ if constexpr (std::is_void_v<RetT>) {+ return {};+ } else {+ return std::move(res).value_or_throw();+ }+ } else { // Apply policy to non-value result+ auto nv = std::move(res).non_value();+ using PolicyRetT = decltype(Policy.value_only_default(std::move(nv)));+ if constexpr (std::is_same_v<PolicyRetT, will_fatal_t>) {+ // Policy terminates (will_fatal), won't return+ Policy.value_only_default(std::move(nv));+ folly::assume_unreachable();+ } else if constexpr (std::is_void_v<RetT>) {+ Policy.value_only_default(std::move(nv));+ return {};+ } else {+ return Policy.value_only_default(std::move(nv));+ }+ }+ }++ RetT await_resume() noexcept { return await_resume_result().value_only(); }++ // IMPORTANT: There is deliberately NO `await_resume_try()` here. This causes+ // `co_awaitTry(value_or_fatal(...))` to INTENTIONALLY fail at compile time:+ // - `value_or_fatal` guarantees value-only completion+ // - `Try`'s error state would be useless (never populated)+ // - We want new code to adopt `result`, not `Try`+ void await_resume_try(auto&&...) = delete;+};++template <typename, auto>+class ValueOrFatalAwaitable;++template <auto Policy>+struct ValueOrFatalAwaitableWithPolicy {+ template <typename T>+ using apply = ValueOrFatalAwaitable<T, Policy>;+};++/// `value_or_fatal<>` / `ValueOrFatalAwaitable<>` compose properly with other+/// coro- and awaitable-wrappers. But, not all combinations make sense -- see+/// the test, and/or extend it if needed. For example, the outer wrapper is+/// useless in `ValueOrFatalAwaitable<...>(co_awaitTry(...))`, since exceptions+/// would already have been routed into a `Try`.+template <typename T, auto Policy>+class [[FOLLY_ATTR_CLANG_CORO_AWAIT_ELIDABLE]] ValueOrFatalAwaitable+ : public CommutativeWrapperAwaitable<+ ValueOrFatalAwaitableWithPolicy<Policy>::template apply,+ T> {+ public:+ using CommutativeWrapperAwaitable<+ ValueOrFatalAwaitableWithPolicy<Policy>::template apply,+ T>::CommutativeWrapperAwaitable;++ template <typename T2 = T, std::enable_if_t<is_awaitable_v<T2>, int> = 0>+ ValueOrFatalAwaiter<T, Policy> operator co_await() && {+ return ValueOrFatalAwaiter<T, Policy>{std::move(this->inner_)};+ }++ using folly_private_value_only_awaitable_t = std::true_type;+};++} // namespace detail++#if FOLLY_HAS_IMMOVABLE_COROUTINES++template <typename Inner, auto Policy>+class value_or_fatal;++namespace detail {++// Configuration for wrapping TaskWithExecutor-like types+template <typename Inner, auto Policy>+struct value_or_fatal_with_executor_cfg {+ using InnerTaskWithExecutorT = Inner;+ using WrapperTaskT = value_or_fatal<+ typename Inner::folly_private_task_without_executor_t,+ Policy>;+ template <typename Awaitable>+ static inline auto wrapAwaitable(Awaitable&& awaitable) noexcept {+ static_assert(!value_only_awaitable_v<Awaitable>); // Don't double-wrap+ return detail::ValueOrFatalAwaitable<Awaitable, Policy>{+ folly::ext::must_use_immediately_unsafe_mover(+ static_cast<Awaitable&&>(awaitable))()};+ }+};++template <typename Inner, auto Policy>+using value_or_fatal_with_executor_base = TaskWithExecutorWrapperCrtp<+ value_or_fatal<Inner, Policy>,+ value_or_fatal_with_executor_cfg<Inner, Policy>>;++template <typename... BaseArgs>+class value_or_fatal_task_promise_wrapper final+ : public TaskPromiseWrapper<BaseArgs...> {};++// Configuration for wrapping Task-like types+template <typename Inner, auto Policy>+struct value_or_fatal_cfg {+ using ValueT = semi_await_result_t<Inner>;+ using InnerTaskT = Inner;+ using TaskWithExecutorT = value_or_fatal<+ decltype(co_withExecutor(+ FOLLY_DECLVAL(Executor::KeepAlive<>), FOLLY_DECLVAL(Inner))),+ Policy>;+ using PromiseT = value_or_fatal_task_promise_wrapper<+ ValueT,+ value_or_fatal<Inner, Policy>,+ typename folly::coro::coroutine_traits<Inner>::promise_type>;+ template <typename Awaitable>+ static inline auto wrapAwaitable(Awaitable&& awaitable) noexcept {+ static_assert(!value_only_awaitable_v<Awaitable>); // Don't double-wrap+ return detail::ValueOrFatalAwaitable<Awaitable, Policy>{+ static_cast<Awaitable&&>(awaitable)};+ }+};++template <typename Inner, auto Policy>+using value_or_fatal_base = TaskWrapperCrtp<+ value_or_fatal<Inner, Policy>,+ value_or_fatal_cfg<Inner, Policy>>;++// Selects base class based on whether Inner has an attached executor+template <typename Inner, auto Policy>+using value_or_fatal_auto_base = conditional_t<+ is_task_with_executor_v<Inner>,+ value_or_fatal_with_executor_base<Inner, Policy>,+ value_or_fatal_base<Inner, Policy>>;++// CAUTION: `value_or_fatal_rewrapper` gives you the power to wrap and unwrap+// `value_or_fatal`, so you must be extremely careful to preserve behavior:+// - The unwrapped task must be rewrapped before awaiting.+// - You must not wrap any other task.++template <typename>+struct value_or_fatal_rewrapper {+ static inline constexpr bool value_or_fatal_wrapped = false;+ static auto wrap_with(auto fn) { return fn(); }+};++template <typename Inner, auto Policy>+struct value_or_fatal_rewrapper<value_or_fatal<Inner, Policy>> {+ static inline constexpr bool value_or_fatal_wrapped = true;+ static Inner unwrapTask(value_or_fatal<Inner, Policy>&& t) {+ return std::move(t).unwrapTask();+ }+ static auto wrap_with(auto fn) {+ return value_or_fatal<decltype(fn()), Policy>{fn()};+ }+};++} // namespace detail++/// `value_or_fatal<Task<T>, Policy>` wraps a task to guarantee value-only+/// completion.+template <typename Inner, auto Policy>+class FOLLY_CORO_TASK_ATTRS value_or_fatal final+ : public detail::value_or_fatal_auto_base<Inner, Policy> {+ protected:+ using detail::value_or_fatal_auto_base<Inner, Policy>::+ value_or_fatal_auto_base;++ template <typename>+ friend struct detail::value_or_fatal_rewrapper;++ public:+ using folly_private_value_only_awaitable_t = std::true_type;+};++#endif // FOLLY_HAS_IMMOVABLE_COROUTINES++} // namespace folly::coro++#endif // FOLLY_HAS_COROUTINES
@@ -20,7 +20,6 @@ #include <folly/Executor.h> #include <folly/Traits.h>-#include <folly/coro/AwaitImmediately.h> #include <folly/coro/Coroutine.h> #include <folly/coro/Traits.h> #include <folly/coro/WithAsyncStack.h>@@ -28,6 +27,7 @@ #include <folly/coro/detail/Malloc.h> #include <folly/io/async/Request.h> #include <folly/lang/CustomizationPoint.h>+#include <folly/lang/MustUseImmediately.h> #include <folly/lang/SafeAlias-fwd.h> #include <folly/tracing/AsyncStack.h> @@ -113,7 +113,7 @@ public: class promise_type final : public ViaCoroutinePromiseBase,- public ExtendedCoroutinePromise {+ public ExtendedCoroutinePromiseCrtp<promise_type> { struct FinalAwaiter { bool await_ready() noexcept { return false; } @@ -154,14 +154,14 @@ folly::AsyncStackFrame& getLeafFrame() noexcept { return leafFrame_; } - std::pair<ExtendedCoroutineHandle, AsyncStackFrame*> getErrorHandle(- exception_wrapper& ex) final {- auto [handle, frame] = continuation_.getErrorHandle(ex);- setContinuation(handle);+ static std::optional<ExtendedCoroutineHandle::ErrorHandle>+ getErrorHandleImpl(promise_type& me, exception_wrapper& ex) {+ auto [handle, frame] = me.continuation_.getErrorHandle(ex);+ me.setContinuation(handle); if (frame && IsStackAware) {- leafFrame_.setParentFrame(*frame);+ me.leafFrame_.setParentFrame(*frame); }- return {coroutine_handle<promise_type>::from_promise(*this), nullptr};+ return std::nullopt; } }; @@ -240,8 +240,9 @@ explicit StackAwareViaIfAsyncAwaiter( folly::Executor::KeepAlive<> executor, Awaitable&& awaitable) : viaCoroutine_(CoroutineType::create(std::move(executor))),- awaitable_(folly::coro::co_withAsyncStack(- static_cast<Awaitable&&>(awaitable))),+ awaitable_(+ folly::coro::co_withAsyncStack(+ static_cast<Awaitable&&>(awaitable))), awaiter_( get_awaiter(static_cast<WithAsyncStackAwaitable&&>(awaitable_))) {} @@ -517,7 +518,7 @@ std::enable_if_t< is_awaitable_v<Awaitable> && !HasViaIfAsyncMethod<Awaitable>::value, int> = 0,- std::enable_if_t<!must_await_immediately_v<Awaitable>, int> = 0>+ std::enable_if_t<!folly::ext::must_use_immediately_v<Awaitable>, int> = 0> auto co_viaIfAsync(folly::Executor::KeepAlive<> executor, Awaitable&& awaitable) -> ViaIfAsyncAwaitable<Awaitable> { return ViaIfAsyncAwaitable<Awaitable>{@@ -528,7 +529,7 @@ std::enable_if_t< is_awaitable_v<Awaitable> && !HasViaIfAsyncMethod<Awaitable>::value, int> = 0,- std::enable_if_t<must_await_immediately_v<Awaitable>, int> = 0>+ std::enable_if_t<folly::ext::must_use_immediately_v<Awaitable>, int> = 0> auto co_viaIfAsync(folly::Executor::KeepAlive<> executor, Awaitable awaitable) -> ViaIfAsyncAwaitable<Awaitable> { return ViaIfAsyncAwaitable<Awaitable>{@@ -538,7 +539,7 @@ struct ViaIfAsyncFunction { template < typename Awaitable,- std::enable_if_t<!must_await_immediately_v<Awaitable>, int> = 0>+ std::enable_if_t<!folly::ext::must_use_immediately_v<Awaitable>, int> = 0> auto operator()(folly::Executor::KeepAlive<> executor, Awaitable&& awaitable) const noexcept(noexcept(co_viaIfAsync( std::move(executor), static_cast<Awaitable&&>(awaitable))))@@ -549,17 +550,19 @@ } template < typename Awaitable,- std::enable_if_t<must_await_immediately_v<Awaitable>, int> = 0>+ std::enable_if_t<folly::ext::must_use_immediately_v<Awaitable>, int> = 0> auto operator()(folly::Executor::KeepAlive<> executor, Awaitable awaitable) const noexcept(noexcept(co_viaIfAsync( std::move(executor),- mustAwaitImmediatelyUnsafeMover(std::move(awaitable))())))+ folly::ext::must_use_immediately_unsafe_mover(+ std::move(awaitable))()))) -> decltype(co_viaIfAsync( std::move(executor),- mustAwaitImmediatelyUnsafeMover(std::move(awaitable))())) {+ folly::ext::must_use_immediately_unsafe_mover(+ std::move(awaitable))())) { return co_viaIfAsync( std::move(executor),- mustAwaitImmediatelyUnsafeMover(std::move(awaitable))());+ folly::ext::must_use_immediately_unsafe_mover(std::move(awaitable))()); } }; @@ -598,35 +601,73 @@ namespace detail { +/// Mixin for awaiters that forward to an inner awaiter with value-only+/// semantics. Provides `await_ready()` and `await_suspend()` with static+/// asserts that both are noexcept. Derived classes implement `await_resume()`.+///+/// GUIDANCE FOR NEW AWAITABLES: Design `await_ready` and `await_suspend` to+/// be noexcept. If they must check for invariant violations:+/// - If feasible, push invariant failures into `await_resume()`.+/// - Otherwise, debug-fatal on violations, and throw an error with a clearly+/// "do not catch me" naming in opt.+template <typename Awaitable>+class ValueOnlyAwaiterBase {+ public:+ using Awaiter = awaiter_type_t<Awaitable>;++ protected:+ Awaiter awaiter_;++ explicit ValueOnlyAwaiterBase(Awaitable&& awaitable)+ : awaiter_(get_awaiter(static_cast<Awaitable&&>(awaitable))) {}++ public:+ auto await_ready() noexcept -> decltype(awaiter_.await_ready()) {+ static_assert(+ noexcept(FOLLY_DECLVAL(Awaiter&).await_ready()),+ "value-only await requires noexcept await_ready(); see class doc");+ return awaiter_.await_ready();+ }++ template <typename Promise>+ auto await_suspend(coroutine_handle<Promise> coro) noexcept+ -> decltype(awaiter_.await_suspend(coro)) {+ static_assert(+ noexcept(awaiter_.await_suspend(coro)),+ "value-only await requires noexcept await_suspend(); see class doc");+ return awaiter_.await_suspend(coro);+ }+};+ template <typename T>-using noexcept_awaitable_of_ = typename T::folly_private_noexcept_awaitable_t;+using value_only_awaitable_of_ =+ typename T::folly_private_value_only_awaitable_t; template <typename Void, typename T>-struct noexcept_awaitable_ {- static_assert(require_sizeof<T>, "`noexcept_awaitable_t` on incomplete type");+struct value_only_awaitable_ {+ static_assert(+ require_sizeof<T>, "`value_only_awaitable_t` on incomplete type"); using type = std::false_type; }; template <>-struct noexcept_awaitable_<void, void> {+struct value_only_awaitable_<void, void> { using type = std::false_type; }; template <typename T>-struct noexcept_awaitable_<void_t<noexcept_awaitable_of_<T>>, T> {- using type = noexcept_awaitable_of_<T>;+struct value_only_awaitable_<void_t<value_only_awaitable_of_<T>>, T> {+ using type = value_only_awaitable_of_<T>; }; } // namespace detail -// This trait is in `ViaIfAsync.h` so that we don't have include `Noexcept.h`-// If there's ever a use-case that doesn't depend on `ViaIfAsync.h`, this can-// be moved up to `Traits.h`+/// True for awaitables that only complete with value (not stopped or error) template <typename T>-using noexcept_awaitable_t =- typename detail::noexcept_awaitable_<void, T>::type;+using value_only_awaitable_t =+ typename detail::value_only_awaitable_<void, T>::type; template <typename T>-inline constexpr bool noexcept_awaitable_v = noexcept_awaitable_t<T>::value;+inline constexpr bool value_only_awaitable_v = value_only_awaitable_t<T>::value; namespace detail { @@ -641,35 +682,15 @@ constexpr bool is_awaitable_try = is_awaiter_try<awaiter_type_t<Awaitable>>; template <typename Awaitable>-class TryAwaiter {- static_assert(is_awaitable_try<Awaitable&&>);-- using Awaiter = awaiter_type_t<Awaitable>;-+class TryAwaiter : public ValueOnlyAwaiterBase<Awaitable> { public:- explicit TryAwaiter(Awaitable&& awaiter)- : awaiter_(get_awaiter(static_cast<Awaitable&&>(awaiter))) {}-- auto await_ready() noexcept(noexcept(std::declval<Awaiter&>().await_ready()))- -> decltype(std::declval<Awaiter&>().await_ready()) {- return awaiter_.await_ready();- }-- template <typename Promise>- auto await_suspend(coroutine_handle<Promise> coro) noexcept(- noexcept(std::declval<Awaiter&>().await_suspend(coro)))- -> decltype(std::declval<Awaiter&>().await_suspend(coro)) {- return awaiter_.await_suspend(coro);- }+ static_assert(is_awaitable_try<Awaitable&&>); - auto await_resume() noexcept(- noexcept(std::declval<Awaiter&>().await_resume_try()))- -> decltype(std::declval<Awaiter&>().await_resume_try()) {- return awaiter_.await_resume_try();- }+ explicit TryAwaiter(Awaitable&& awaitable)+ : ValueOnlyAwaiterBase<Awaitable>(static_cast<Awaitable&&>(awaitable)) {} - private:- Awaiter awaiter_;+ auto await_resume()+ FOLLY_DETAIL_FORWARD_BODY(this->awaiter_.await_resume_try()) }; /**@@ -682,51 +703,60 @@ public: template < typename T2,- std::enable_if_t<!must_await_immediately_v<T2>, int> = 0>+ std::enable_if_t<!folly::ext::must_use_immediately_v<T2>, int> = 0> explicit CommutativeWrapperAwaitable(T2&& awaitable) noexcept( std::is_nothrow_constructible_v<T, T2>) : inner_(static_cast<T2&&>(awaitable)) {} template < typename T2,- std::enable_if_t<must_await_immediately_v<T2>, int> = 0>- explicit CommutativeWrapperAwaitable(T2 awaitable)- // `mustAwaitImmediatelyUnsafeMover` has more `noexcept` assertions.- noexcept(noexcept(T{FOLLY_DECLVAL(T2)}))- : inner_(mustAwaitImmediatelyUnsafeMover(std::move(awaitable))()) {}+ std::enable_if_t<folly::ext::must_use_immediately_v<T2>, int> = 0>+ explicit CommutativeWrapperAwaitable(T2 awaitable) noexcept(noexcept(T{+ FOLLY_DECLVAL(T2)}))+ : inner_(+ folly::ext::must_use_immediately_unsafe_mover(+ std::move(awaitable))()) {} template <typename Factory> explicit CommutativeWrapperAwaitable(std::in_place_t, Factory&& factory) : inner_(factory()) {} + // Two overloads for the CancellationToken to avoid unnecessary copies+ // (atomic refcount costs).+ //+ // NB: If we merged the overloads into a single template, overload resolution+ // rules would consider it ambiguous wrt the default implementation in+ // `WithCancellation.h`. template < typename T2 = T,- typename Result = decltype(folly::coro::co_withCancellation(- FOLLY_DECLVAL(const folly::CancellationToken&), FOLLY_DECLVAL(T2&&)))>+ // "WART:" in `WithCancellation.h` explains the remove-reference+ typename Result =+ std::remove_reference_t<decltype(folly::coro::co_withCancellation(+ FOLLY_DECLVAL(const folly::CancellationToken&),+ FOLLY_DECLVAL(T2)))>> friend Derived<Result> co_withCancellation(- const folly::CancellationToken& cancelToken, Derived<T>&& awaitable) {+ const folly::CancellationToken& cancelToken, Derived<T> awaitable) { return Derived<Result>{- std::in_place, [&]() -> decltype(auto) {+ std::in_place, [&]() { return folly::coro::co_withCancellation(- cancelToken, static_cast<T&&>(awaitable.inner_));+ cancelToken,+ folly::ext::must_use_immediately_unsafe_mover(+ std::move(awaitable.inner_))()); }}; }- // This overload exists to avoid unnecessarily copying `cancelToken`, which- // has atomic refcount costs.- // - Taking it by-value would force unnecessary token copies for underlying- // awaitables that ignore the token.- // - If we merged the overloads into a single template, overload resolution- // rules would consider it ambiguous wrt the default implementation in- // `WithCancellation.h`. template < typename T2 = T,- typename Result = decltype(folly::coro::co_withCancellation(- FOLLY_DECLVAL(folly::CancellationToken&&), FOLLY_DECLVAL(T2&&)))>+ // "WART:" in `WithCancellation.h` explains the remove-reference+ typename Result =+ std::remove_reference_t<decltype(folly::coro::co_withCancellation(+ FOLLY_DECLVAL(folly::CancellationToken&&), FOLLY_DECLVAL(T2)))>> friend Derived<Result> co_withCancellation(- folly::CancellationToken&& cancelToken, Derived<T>&& awaitable) {+ folly::CancellationToken&& cancelToken, Derived<T> awaitable) { return Derived<Result>{- std::in_place, [&]() -> decltype(auto) {+ std::in_place, [&]() { return folly::coro::co_withCancellation(- std::move(cancelToken), static_cast<T&&>(awaitable.inner_));+ std::move(cancelToken),+ folly::ext::must_use_immediately_unsafe_mover(+ std::move(awaitable.inner_))()); }}; } @@ -744,25 +774,7 @@ }}; } - template <- typename T2 = T,- std::enable_if_t<!must_await_immediately_v<T2>, int> = 0,- typename Result = semi_await_awaitable_t<T2>>- friend Derived<Result> co_viaIfAsync(- folly::Executor::KeepAlive<> executor,- Derived<T>&& awaitable) //- noexcept(noexcept(folly::coro::co_viaIfAsync(- FOLLY_DECLVAL(folly::Executor::KeepAlive<>), FOLLY_DECLVAL(T2)))) {- return Derived<Result>{- std::in_place, [&]() -> decltype(auto) {- return folly::coro::co_viaIfAsync(- std::move(executor), static_cast<T&&>(awaitable.inner_));- }};- }- template <- typename T2 = T,- std::enable_if_t<must_await_immediately_v<T2>, int> = 0,- typename Result = semi_await_awaitable_t<T2>>+ template <typename T2 = T, typename Result = semi_await_awaitable_t<T2>> friend Derived<Result> co_viaIfAsync( folly::Executor::KeepAlive<> executor, Derived<T> awaitable) //@@ -772,36 +784,66 @@ std::in_place, [&]() { return folly::coro::co_viaIfAsync( std::move(executor),- mustAwaitImmediatelyUnsafeMover(std::move(awaitable.inner_))());+ folly::ext::must_use_immediately_unsafe_mover(+ std::move(awaitable.inner_))()); }}; } - template <- typename T2 = T,- typename = decltype(FOLLY_DECLVAL(T2&&).getUnsafeMover(- FOLLY_DECLVAL(ForMustAwaitImmediately)))>- auto getUnsafeMover(ForMustAwaitImmediately p) && noexcept {- // See "A note on object slicing" above `mustAwaitImmediatelyUnsafeMover`- static_assert(sizeof(Derived<T>) == sizeof(T));- static_assert( // More `noexcept` tests in `MustAwaitImmediatelyUnsafeMover`- noexcept(std::move(inner_).getUnsafeMover(p)));- return MustAwaitImmediatelyUnsafeMover{- (Derived<T>*)nullptr, std::move(inner_).getUnsafeMover(p)};- }- // IMPORTANT: If a commutative wrapper changes safety, immediate- or // noexcept-awaitability, it must remember to override these:- using folly_private_must_await_immediately_t = must_await_immediately_t<T>;- using folly_private_noexcept_awaitable_t = noexcept_awaitable_t<T>;- using folly_private_safe_alias_t = safe_alias_of<T>;+ using folly_must_use_immediately_t = ext::must_use_immediately_t<T>;+ using folly_private_value_only_awaitable_t = value_only_awaitable_t<T>;+ template <safe_alias Default>+ using folly_private_safe_alias_t = safe_alias_of<T, Default>; protected: T inner_;++ private:+ template <typename U>+ using my_curried_mover = folly::ext::curried_unsafe_mover_t<+ U,+ decltype(folly::ext::must_use_immediately_unsafe_mover(+ FOLLY_DECLVAL(T)))>;++ public:+ template <+ typename Me, // not a forwarding ref, see SFINAE+ typename T2 = T,+ std::enable_if_t<+ // This check guards against misuse (+ fails on lvalue refs)+ // See `wrap_must_use_immediately_t::unsafe_mover` for more context+ std::is_base_of_v<CommutativeWrapperAwaitable, Me> &&+ // Without this check we might instantiate this for things like+ // `TryAwaitable<coro::Future<...>&&>`, erroring with:+ // "cannot form a pointer-to-member to member of reference type"+ folly::ext::must_use_immediately_v<T2>,+ int> = 0>+ static my_curried_mover<Me> unsafe_mover(+ folly::ext::must_use_immediately_private_t, Me&& me) noexcept {+ return folly::ext::curried_unsafe_mover_from_bases_and_members<+ CommutativeWrapperAwaitable>(+ folly::tag</*no bases*/>,+ folly::vtag<&CommutativeWrapperAwaitable::inner_>,+ static_cast<Me&&>(me));+ }+ template <+ typename DerivedFromMe,+ // Matches the SFINAE logic in our `unsafe_mover`+ std::enable_if_t<+ std::is_base_of_v<CommutativeWrapperAwaitable, DerivedFromMe>,+ int> = 0>+ explicit CommutativeWrapperAwaitable(+ folly::ext::curried_unsafe_mover_private_t,+ my_curried_mover<DerivedFromMe>&& mover)+ // `must_use_immediately_unsafe_mover` has more `noexcept` assertions+ noexcept(noexcept(T{std::move(mover.template get<0>())()}))+ : inner_{std::move(mover.template get<0>())()} {} }; template <typename T>-class [[FOLLY_ATTR_CLANG_CORO_AWAIT_ELIDABLE]] TryAwaitable- : public CommutativeWrapperAwaitable<TryAwaitable, T> {+class [[FOLLY_ATTR_CLANG_CORO_AWAIT_ELIDABLE]]+TryAwaitable : public CommutativeWrapperAwaitable<TryAwaitable, T> { public: using CommutativeWrapperAwaitable<TryAwaitable, T>:: CommutativeWrapperAwaitable;@@ -813,18 +855,18 @@ int> = 0, typename T2 = like_t<Self, T>, std::enable_if_t<is_awaitable_v<T2>, int> = 0>- friend TryAwaiter<T2> operator co_await(Self && self) {+ friend TryAwaiter<T2> operator co_await(Self&& self) { return TryAwaiter<T2>{static_cast<Self&&>(self).inner_}; } - using folly_private_noexcept_awaitable_t = std::true_type;+ using folly_private_value_only_awaitable_t = std::true_type; }; } // namespace detail template < typename Awaitable,- std::enable_if_t<!must_await_immediately_v<Awaitable>, int> = 0>+ std::enable_if_t<!folly::ext::must_use_immediately_v<Awaitable>, int> = 0> detail::TryAwaitable<remove_cvref_t<Awaitable>> co_awaitTry( [[FOLLY_ATTR_CLANG_CORO_AWAIT_ELIDABLE_ARGUMENT]] Awaitable&& awaitable) { return detail::TryAwaitable<remove_cvref_t<Awaitable>>{@@ -832,47 +874,16 @@ } template < typename Awaitable,- std::enable_if_t<must_await_immediately_v<Awaitable>, int> = 0>+ std::enable_if_t<folly::ext::must_use_immediately_v<Awaitable>, int> = 0> detail::TryAwaitable<Awaitable> co_awaitTry( [[FOLLY_ATTR_CLANG_CORO_AWAIT_ELIDABLE_ARGUMENT]] Awaitable awaitable) { return detail::TryAwaitable<Awaitable>{- mustAwaitImmediatelyUnsafeMover(std::move(awaitable))()};+ folly::ext::must_use_immediately_unsafe_mover(std::move(awaitable))()}; } template <typename T> using semi_await_try_result_t = await_result_t<semi_await_awaitable_t< decltype(folly::coro::co_awaitTry(FOLLY_DECLVAL(T)))>>;--namespace detail {--template <typename T>-class [[FOLLY_ATTR_CLANG_CORO_AWAIT_ELIDABLE]] NothrowAwaitable- : public CommutativeWrapperAwaitable<NothrowAwaitable, T> {- public:- using CommutativeWrapperAwaitable<NothrowAwaitable, T>::- CommutativeWrapperAwaitable;-- T&& unwrap() { return std::move(this->inner_); }-};--} // namespace detail--template <- typename Awaitable,- std::enable_if_t<!must_await_immediately_v<Awaitable>, int> = 0>-detail::NothrowAwaitable<remove_cvref_t<Awaitable>> co_nothrow(- [[FOLLY_ATTR_CLANG_CORO_AWAIT_ELIDABLE_ARGUMENT]] Awaitable&& awaitable) {- return detail::NothrowAwaitable<remove_cvref_t<Awaitable>>{- static_cast<Awaitable&&>(awaitable)};-}-template <- typename Awaitable,- std::enable_if_t<must_await_immediately_v<Awaitable>, int> = 0>-detail::NothrowAwaitable<remove_cvref_t<Awaitable>> co_nothrow(- [[FOLLY_ATTR_CLANG_CORO_AWAIT_ELIDABLE_ARGUMENT]] Awaitable awaitable) {- return detail::NothrowAwaitable<remove_cvref_t<Awaitable>>{- mustAwaitImmediatelyUnsafeMover(std::move(awaitable))()};-} } // namespace folly::coro
@@ -116,6 +116,10 @@ class WithAsyncStackAwaiter { using Awaiter = awaiter_type_t<Awaitable>; + static constexpr bool kSuspendIsNoexcept =+ noexcept(FOLLY_DECLVAL(Awaiter&).await_suspend(FOLLY_DECLVAL(+ coroutine_handle<WithAsyncStackCoroutine::promise_type>)));+ public: explicit WithAsyncStackAwaiter(Awaitable&& awaitable) : awaiter_(get_awaiter(static_cast<Awaitable&&>(awaitable))),@@ -129,7 +133,8 @@ // needs to be no-inline as return address is being captured for async stack // tracing template <typename Promise>- FOLLY_NOINLINE auto await_suspend(coroutine_handle<Promise> h) {+ FOLLY_NOINLINE auto await_suspend(coroutine_handle<Promise> h) noexcept(+ kSuspendIsNoexcept) { AsyncStackFrame& callerFrame = h.promise().getAsyncFrame(); AsyncStackRoot* stackRoot = callerFrame.getStackRoot(); assert(stackRoot != nullptr);@@ -143,7 +148,7 @@ using await_suspend_result_t = decltype(awaiter_.await_suspend(wrapperHandle)); - try {+ auto doSuspend = [&]() { if constexpr (std::is_same_v<await_suspend_result_t, bool>) { if (!awaiter_.await_suspend(wrapperHandle)) { folly::activateAsyncStackFrame(*stackRoot, callerFrame);@@ -154,10 +159,19 @@ } else { return awaiter_.await_suspend(wrapperHandle); }- } catch (...) {- folly::activateAsyncStackFrame(*stackRoot, callerFrame);- folly::deactivateSuspendedLeaf(coroWrapper_.getLeafFrame());- throw;+ };++ if constexpr (kSuspendIsNoexcept) {+ return doSuspend();+ } else {+ // Restore async stack state on exception, then rethrow.+ try {+ return doSuspend();+ } catch (...) {+ folly::activateAsyncStackFrame(*stackRoot, callerFrame);+ folly::deactivateSuspendedLeaf(coroWrapper_.getLeafFrame());+ throw;+ } } }
@@ -17,9 +17,9 @@ #pragma once #include <folly/CancellationToken.h>-#include <folly/coro/AwaitImmediately.h> #include <folly/coro/Coroutine.h> #include <folly/lang/CustomizationPoint.h>+#include <folly/lang/MustUseImmediately.h> #if FOLLY_HAS_COROUTINES @@ -41,23 +41,27 @@ /// Types must opt-in to hooking cancellation by customising this function. template < typename Awaitable,- std::enable_if_t<!must_await_immediately_v<Awaitable>, int> = 0>+ std::enable_if_t<!folly::ext::must_use_immediately_v<Awaitable>, int> = 0>+// WART: The `Awaitable&&` overload must return `Awaitable&&` to support+// `Baton`s, which is immovable and awaitable only by lvalue. We could remove+// this, and simplify other code, by migrating `Baton`s to emit an explicit+// must-use-immediately awaitable instead of awaiting them by lvalue. Awaitable&& co_withCancellation( const folly::CancellationToken&, Awaitable&& awaitable) noexcept { return static_cast<Awaitable&&>(awaitable); } template < typename Awaitable,- std::enable_if_t<must_await_immediately_v<Awaitable>, int> = 0>+ std::enable_if_t<folly::ext::must_use_immediately_v<Awaitable>, int> = 0> Awaitable co_withCancellation( const folly::CancellationToken&, Awaitable awaitable) noexcept {- return mustAwaitImmediatelyUnsafeMover(std::move(awaitable))();+ return folly::ext::must_use_immediately_unsafe_mover(std::move(awaitable))(); } struct WithCancellationFunction { template < typename Awaitable,- std::enable_if_t<!must_await_immediately_v<Awaitable>, int> = 0>+ std::enable_if_t<!folly::ext::must_use_immediately_v<Awaitable>, int> = 0> auto operator()( const folly::CancellationToken& cancelToken, Awaitable&& awaitable) const noexcept(noexcept(co_withCancellation(@@ -69,21 +73,24 @@ } template < typename Awaitable,- std::enable_if_t<must_await_immediately_v<Awaitable>, int> = 0>+ std::enable_if_t<folly::ext::must_use_immediately_v<Awaitable>, int> = 0> auto operator()( const folly::CancellationToken& cancelToken, Awaitable awaitable) const noexcept(noexcept(co_withCancellation( cancelToken,- mustAwaitImmediatelyUnsafeMover(std::move(awaitable))())))+ folly::ext::must_use_immediately_unsafe_mover(+ std::move(awaitable))()))) -> decltype(co_withCancellation( cancelToken,- mustAwaitImmediatelyUnsafeMover(std::move(awaitable))())) {+ folly::ext::must_use_immediately_unsafe_mover(+ std::move(awaitable))())) { return co_withCancellation(- cancelToken, mustAwaitImmediatelyUnsafeMover(std::move(awaitable))());+ cancelToken,+ folly::ext::must_use_immediately_unsafe_mover(std::move(awaitable))()); } template < typename Awaitable,- std::enable_if_t<!must_await_immediately_v<Awaitable>, int> = 0>+ std::enable_if_t<!folly::ext::must_use_immediately_v<Awaitable>, int> = 0> auto operator()(folly::CancellationToken&& cancelToken, Awaitable&& awaitable) const noexcept(noexcept(co_withCancellation( std::move(cancelToken), static_cast<Awaitable&&>(awaitable))))@@ -94,17 +101,19 @@ } template < typename Awaitable,- std::enable_if_t<must_await_immediately_v<Awaitable>, int> = 0>+ std::enable_if_t<folly::ext::must_use_immediately_v<Awaitable>, int> = 0> auto operator()(folly::CancellationToken&& cancelToken, Awaitable awaitable) const noexcept(noexcept(co_withCancellation( std::move(cancelToken),- mustAwaitImmediatelyUnsafeMover(std::move(awaitable))())))+ folly::ext::must_use_immediately_unsafe_mover(+ std::move(awaitable))()))) -> decltype(co_withCancellation( std::move(cancelToken),- mustAwaitImmediatelyUnsafeMover(std::move(awaitable))())) {+ folly::ext::must_use_immediately_unsafe_mover(+ std::move(awaitable))())) { return co_withCancellation( std::move(cancelToken),- mustAwaitImmediatelyUnsafeMover(std::move(awaitable))());+ folly::ext::must_use_immediately_unsafe_mover(std::move(awaitable))()); } }; } // namespace adl
@@ -21,14 +21,15 @@ /// For functions-of-coros, like `timeout()` or `collectAll()`, we want the /// outer coro to be able to pass through these attributes of the inner coro:-/// - `must_await_immediately_v`-/// - `noexcept_awaitable_v`-/// - `safe_alias_of_v`+/// - `folly::ext::must_use_immediately_v`+/// - `value_only_awaitable_v`+/// - `strict_safe_alias_of_v` / `lenient_safe_alias_of_v` /// /// Variation along these dimensions is currently implemented as a zoo of coro-/// templates and wrappers -- `Task` aka `UnsafeMovableTask`, `NowTask`,-/// `SafeTask`, `AsNoexcept<InnerTask>`. The type function `PickTaskWrapper`-/// provides common logic for picking a task type with the given attributes.+/// templates and wrappers -- `Task` aka `UnsafeMovableTask`, `now_task`,+/// `safe_task`, `value_or_fatal<InnerTask>`. The type function+/// `pick_task_wrapper` provides common logic for picking a task type with the+/// given attributes. #if FOLLY_HAS_COROUTINES @@ -40,17 +41,17 @@ class TaskWithExecutor; template <safe_alias, typename>-class SafeTask;+class safe_task; template <safe_alias, typename>-class SafeTaskWithExecutor;+class safe_task_with_executor; template <typename T>-class NowTask;+class now_task; template <typename T>-class NowTaskWithExecutor;+class now_task_with_executor; template <typename, auto>-class AsNoexcept;+class value_or_fatal; namespace detail { @@ -59,13 +60,13 @@ using apply = T; }; -template <safe_alias, bool /*must await immediately (now)*/>-struct PickTaskWrapperImpl;+template <safe_alias, bool /*must use immediately (now)*/>+struct pick_task_wrapper_impl; #if FOLLY_HAS_IMMOVABLE_COROUTINES template <>-struct PickTaskWrapperImpl<safe_alias::unsafe, /*await now*/ false> {+struct pick_task_wrapper_impl<safe_alias::unsafe, /*await now*/ false> { template <typename T> using Task = Task<T>; template <typename T>@@ -73,37 +74,37 @@ }; template <>-struct PickTaskWrapperImpl<safe_alias::unsafe, /*await now*/ true> {+struct pick_task_wrapper_impl<safe_alias::unsafe, /*await now*/ true> { template <typename T>- using Task = NowTask<T>;+ using Task = now_task<T>; template <typename T>- using TaskWithExecutor = NowTaskWithExecutor<T>;+ using TaskWithExecutor = now_task_with_executor<T>; }; -// These `SafeTask` types are immovable, so "await now" doesn't matter.+// These `safe_task` types are immovable, so "await now" doesn't matter. template <safe_alias Safety, bool AwaitNow> requires(Safety < safe_alias::closure_min_arg_safety)-struct PickTaskWrapperImpl<Safety, AwaitNow> {+struct pick_task_wrapper_impl<Safety, AwaitNow> { template <typename T>- using Task = SafeTask<Safety, T>;+ using Task = safe_task<Safety, T>; template <typename T>- using TaskWithExecutor = SafeTaskWithExecutor<Safety, T>;+ using TaskWithExecutor = safe_task_with_executor<Safety, T>; }; template <safe_alias Safety> requires(Safety >= safe_alias::closure_min_arg_safety)-// Future: There is no principled reason we can't have must-await-immediately-// `SafeTask`s with these higher safety levels, but supporting that cleanly+// Future: There is no principled reason we can't have must-use-immediately+// `safe_task`s with these higher safety levels, but supporting that cleanly // would require reorganizing the `folly/coro` task-wrapper implementations. Two // possible approaches are:-// - `NowTask<T> = AwaitNow<Task<T>>`-// - Roll up `NowTask` and `SafeTask` into something like `BasicTask<T, Cfg>`,+// - `now_task<T> = await_now<Task<T>>`+// - Roll up `now_task` and `safe_task` into `basic_task<T, Cfg>` or similar, // where `Cfg` captures both safety & immediate-awaitability.-struct PickTaskWrapperImpl<Safety, /*await now*/ false> {+struct pick_task_wrapper_impl<Safety, /*await now*/ false> { template <typename T>- using Task = SafeTask<Safety, T>;+ using Task = safe_task<Safety, T>; template <typename T>- using TaskWithExecutor = SafeTaskWithExecutor<Safety, T>;+ using TaskWithExecutor = safe_task_with_executor<Safety, T>; }; #else // no FOLLY_HAS_IMMOVABLE_COROUTINES@@ -111,7 +112,7 @@ // This fallback is required because `coro::Future<SafeType>` is safe and is // available on earlier build systems. We have no choice but to emit `Task`. template <safe_alias Safety>-struct PickTaskWrapperImpl<Safety, /*await now*/ false> {+struct pick_task_wrapper_impl<Safety, /*await now*/ false> { template <typename T> using Task = Task<T>; template <typename T>@@ -120,30 +121,32 @@ #endif // FOLLY_HAS_IMMOVABLE_COROUTINES -// Pass this as `AddWrapperMetaFn` to `PickTaskWrapper` to add `AsNoexcept`.+// Pass this as `AddWrapperMetaFn` to `pick_task_wrapper` to add+// `value_or_fatal`. template <auto CancelCfg>-struct AsNoexceptWithCancelCfg {+struct value_or_fatal_with_cancel_cfg { template <typename T>- using apply = AsNoexcept<T, CancelCfg>;+ using apply = value_or_fatal<T, CancelCfg>; }; template < typename T, safe_alias Safety,- bool MustAwaitImmediately,+ bool MustUseImmediately, typename AddWrapperMetaFn = identity_metafunction>-using PickTaskWrapper = typename AddWrapperMetaFn::template apply<- typename PickTaskWrapperImpl<Safety, MustAwaitImmediately>::template Task<+using pick_task_wrapper = typename AddWrapperMetaFn::template apply<+ typename pick_task_wrapper_impl<Safety, MustUseImmediately>::template Task< T>>; template < typename T, safe_alias Safety,- bool MustAwaitImmediately,+ bool MustUseImmediately, typename AddWrapperMetaFn = identity_metafunction>-using PickTaskWithExecutorWrapper = typename AddWrapperMetaFn::template apply<- typename PickTaskWrapperImpl<Safety, MustAwaitImmediately>::- template TaskWithExecutor<T>>;+using pick_task_with_executor_wrapper =+ typename AddWrapperMetaFn::template apply<typename pick_task_wrapper_impl<+ Safety,+ MustUseImmediately>::template TaskWithExecutor<T>>; } // namespace detail } // namespace folly::coro
@@ -25,22 +25,23 @@ /// Learn more about `coro/safe` tools by browsing `docs/`. Start with /// `README.md` and `AsyncClosure.md`. Here's a tl;dr for `AsyncClosure.h`. ///-/// Use `async_closure()` / `async_now_closure()` only when `NowTask` is not+/// Use `async_closure()` / `async_now_closure()` only when `now_task` is not /// enough. For your effort, you get (1) guaranteed, exception-safe async /// RAII, and (2) the resulting coro is an automatically-measured movable-/// `SafeTask`, which improves lifetime safety (`LifetimeSafetyBenefits.md`).+/// `safe_task`, which improves lifetime safety (`LifetimeSafetyBenefits.md`). ///-/// Control flow matches a regular `NowTask` lazy-start coro:+/// Control flow matches a regular `now_task` lazy-start coro: /// - All "argument-binding" and "coro creation" work is eager. /// - Your inner task & subsequent cleanup can only run when awaited. ///-/// `async_closure(bound_args{...}, taskFn)` wraps an outer task around yours,+/// `async_closure(bind::args{...}, taskFn)` wraps an outer task around yours, /// unless elided via an automatic optimization. The outer task owns special /// "capture" args passed to the closure, ensuring they outlive the inner task. /// /// Lifecycle contract:-/// - `bound_args{}` evaluate left-to-right (L2R) due to `{}`.-/// - Construction of `capture_in_place` / `make_in_place` args is also L2R.+/// - `bind::args{}` evaluate left-to-right (L2R) due to `{}`.+/// - Construction of `bind::capture_in_place` / `bind::in_place` args is also+/// L2R. /// - When args are passed to the inner coro, copy/move order is unspecified. /// - Upon awaiting the inner coro, `setParentCancelToken()` is called on the /// capture args in L2R order.@@ -48,7 +49,7 @@ /// - When the outer coro exits, captures are destroyed in R2L order. /// /// The `co_cleanup()` and `setParentCancelToken()` protocols support capture-/// types like `SafeAsyncScope` and `BackgroundTask`, which give the user+/// types like `safe_async_scope` and `BackgroundTask`, which give the user /// guaranteed, exception-safe async cleanup. Before building custom async /// RAII, carefully read `CoCleanupAsyncRAII.md`. ///@@ -56,8 +57,8 @@ /// which implements a similar "async RAII" contract for object scopes. /// /// The difference between `async_closure()` and `async_now_closure()` is that-/// the former measures argument & inner coro safety, and makes a `SafeTask`,-/// while the latter has no safety checks, and makes a `NowTask`. Both make it+/// the former measures argument & inner coro safety, and makes a `safe_task`,+/// while the latter has no safety checks, and makes a `now_task`. Both make it /// easy to write lifetime-safe code. struct async_closure_config {@@ -93,13 +94,11 @@ namespace detail { template <bool ForceOuterCoro, bool EmitNowTask>-// OK to take `bound_args` by-ref since the porcelain functions take it by-value+// OK to take `bind::args` by-ref since the porcelain functions take it by-value auto async_closure_impl(auto&& bargs, auto&& make_inner_coro) { constexpr detail::async_closure_bindings_cfg Cfg{ .force_outer_coro = ForceOuterCoro,- // `NowTask`s closures have no safety controls, and thus -- like- // "shared cleanup" closures -- don't get to upgrade `capture` refs.- .force_shared_cleanup = EmitNowTask,+ .emit_now_task = EmitNowTask, .is_invoke_member = is_instantiation_of_v< invoke_member_wrapper_fn, std::remove_reference_t<decltype(make_inner_coro)>>};@@ -110,8 +109,8 @@ } } // namespace detail -// Makes a `SafeTask` whose safety is determined by the supplied arguments.-// `SafeTask` requires that (1) the inner coroutine must not take arguments+// Makes a `safe_task` whose safety is determined by the supplied arguments.+// `safe_task` requires that (1) the inner coroutine must not take arguments // by-reference, and (2) must have a `maybe_value`-safe return type. // // Caveat: When `make_inner_coro` is a coroutine wrapper, that part is@@ -120,9 +119,9 @@ // Coro creation, argument storage, and in-place construction are also // synchronous, as is the movement of the args into the task coroutine. //-// The first argument should be `bound_args{...}`. For single-argument-// closures, you can omit the `bound_args` if you're passing `as_capture()`,-// `capture_in_place<>()`, or another `like_bound_args` item.+// The first argument should be `bind::args{...}`. For single-argument+// closures, you can omit the `bind::args` if you're passing `bind::capture()`,+// `bind::capture_in_place<>()`, or another `bind::ext::like_args` item. // // Async RAII: Awaiting the task ensures `co_cleanup(async_closure_private_t)` // is awaited for each of the `capture` arguments that defines it.@@ -142,18 +141,18 @@ } // Like `async_closure` -- same argument binding semantics, same `co_cleanup`-// async RAII, and cancellation support, but returns a non-movable `NowTask`+// async RAII, and cancellation support, but returns a non-movable `now_task` // without the lifetime safety enforcement:-// - `make_inner_coro` may return a `NowTask`, plain `Task`, or `SafeTask`.+// - `make_inner_coro` may return a `now_task`, plain `Task`, or `safe_task`. // - It can take arguments by ref, you can pass raw pointers, etc. // - There are no checks on the `co_return` type. // // Requiring the task to be immediately awaited prevents a lot of common // lifetime bugs. If you cannot immediately await the task, then you should-// review `LifetimSafetyBenefits.md` and use the `SafeTask`-enabled-// `async_closure()`, which is movable and schedulable on `SafeAsyncScope`.+// review `LifetimSafetyBenefits.md` and use the `safe_task`-enabled+// `async_closure()`, which is movable and schedulable on `safe_async_scope`. //-// BEWARE: Returning `NowTask` doesn't prevent egregious bugs like returning+// BEWARE: Returning `now_task` doesn't prevent egregious bugs like returning // a pointer to a local. Instead, make sure to configure your compiler to // error on simple, non-async lifetime bugs (e.g. `-Wdangling -Werror`). template <async_closure_config Cfg = async_closure_config{}>
@@ -0,0 +1,159 @@+/*+ * Copyright (c) Meta Platforms, Inc. and affiliates.+ *+ * Licensed under the Apache License, Version 2.0 (the "License");+ * you may not use this file except in compliance with the License.+ * You may obtain a copy of the License at+ *+ * http://www.apache.org/licenses/LICENSE-2.0+ *+ * Unless required by applicable law or agreed to in writing, software+ * distributed under the License is distributed on an "AS IS" BASIS,+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.+ * See the License for the specific language governing permissions and+ * limitations under the License.+ */++#pragma once++#include <folly/lang/bind/Bind.h>++#if FOLLY_HAS_IMMOVABLE_COROUTINES++///+/// Please read the user- and developer-facing docs in `Capture.md`.+///+/// Callers typically only include `BindCaptures.h`, while callees need to+/// include `Captures.h`. Both are provided by `AsyncClosure.h`.+///+/// NOTE:+/// - The binding helpers in this file are all in `folly::bind::` for+/// uniformity with the other related verbs -- `bind::args`,+/// `bind::in_place` `bind::constant`, etc.+/// - The `bind_to_storage_policy` specialization for this is only used+/// in one file, so it's inlined in `BindAsyncClosure.h`.+///++namespace folly::bind {++namespace detail {++// Any binding with this key is meant to be owned by the async closure+enum class capture_kind {+ plain = 0,+ // Syntax sugar: Passing `bind::capture_indirect()` with a pointer-like (e.g.+ // `unique_ptr<T>`), this emits a `capture_indirect<>`, giving access to+ // the underlying `T` with just one dereference `*` / `->`, instead of 2.+ indirect,+};++struct capture_bind_info_t : folly::bind::ext::bind_info_t {+ capture_kind captureKind_;++ constexpr explicit capture_bind_info_t(+ // Using a constraint prevents object slicing+ std::same_as<folly::bind::ext::bind_info_t> auto bi,+ capture_kind ap)+ : folly::bind::ext::bind_info_t(std::move(bi)), captureKind_(ap) {}+};++template <capture_kind Kind, typename UpdateBI = std::identity>+struct capture_bind_info {+ // Using `auto` prevents object slicing+ constexpr auto operator()(auto bi) {+ return capture_bind_info_t{UpdateBI{}(std::move(bi)), Kind};+ }+};++} // namespace detail++///+/// `bind::capture()` and `bind::capture_indirect()` work much like other+/// `folly::bind` modifiers. However, since they're primarily intended+/// for `async_closure` arguments, you will practically only use them:+/// - alone, for non-`co_cleanup` arguments;+/// - with `bind::in_place()` or `bind::in_place_with()`, for `co_cleanup`+/// arguments;+/// - with `constant()`, for either.+///+/// `capture_in_place<T>()` is short for `bind::capture(bind::in_place<T>())`.+///+/// See `Captures.md` and `folly/lang/Bindings.md`.+///++template <typename... Ts>+struct capture+ : ::folly::bind::ext::merge_update_args<+ detail::capture_bind_info<detail::capture_kind::plain>,+ Ts...> {+ using ::folly::bind::ext::merge_update_args<+ detail::capture_bind_info<detail::capture_kind::plain>,+ Ts...>::merge_update_args;+};+template <typename... Ts>+capture(Ts&&...) -> capture<folly::bind::ext::deduce_args_t<Ts>...>;++template <typename... Ts>+struct capture_indirect+ : ::folly::bind::ext::merge_update_args<+ detail::capture_bind_info<detail::capture_kind::indirect>,+ Ts...> {+ using ::folly::bind::ext::merge_update_args<+ detail::capture_bind_info<detail::capture_kind::indirect>,+ Ts...>::merge_update_args;+};+template <typename... Ts>+capture_indirect(Ts&&...)+ -> capture_indirect<folly::bind::ext::deduce_args_t<Ts>...>;++// Sugar for `capture{const_ref{...}}`+template <typename... Ts>+struct capture_const_ref+ : ::folly::bind::ext::merge_update_args<+ detail::capture_bind_info<+ detail::capture_kind::plain,+ ::folly::bind::detail::const_ref_bind_info>,+ Ts...> {+ using ::folly::bind::ext::merge_update_args<+ detail::capture_bind_info<+ detail::capture_kind::plain,+ ::folly::bind::detail::const_ref_bind_info>,+ Ts...>::merge_update_args;+};+template <typename... Ts>+capture_const_ref(Ts&&...)+ -> capture_const_ref<folly::bind::ext::deduce_args_t<Ts>...>;+// Sugar for `capture{mut_ref{...}}`+template <typename... Ts>+struct capture_mut_ref+ : ::folly::bind::ext::merge_update_args<+ detail::capture_bind_info<+ detail::capture_kind::plain,+ ::folly::bind::detail::mut_ref_bind_info>,+ Ts...> {+ using ::folly::bind::ext::merge_update_args<+ detail::capture_bind_info<+ detail::capture_kind::plain,+ ::folly::bind::detail::mut_ref_bind_info>,+ Ts...>::merge_update_args;+};+template <typename... Ts>+capture_mut_ref(Ts&&...)+ -> capture_mut_ref<folly::bind::ext::deduce_args_t<Ts>...>;++// Sugar for `capture{in_place<T>(...)}`+template <typename T>+auto capture_in_place(auto&&... as [[clang::lifetimebound]]) {+ return capture(bind::in_place<T>(static_cast<decltype(as)>(as)...));+}+// Sugar for `capture{in_place_with(fn, ...)}`+auto capture_in_place_with(+ auto make_fn, auto&&... as [[clang::lifetimebound]]) {+ return capture(+ bind::in_place_with(+ std::move(make_fn), static_cast<decltype(as)>(as)...));+}++} // namespace folly::bind++#endif
@@ -16,6 +16,7 @@ #pragma once +#include <folly/Portability.h> #include <folly/Traits.h> #include <folly/Utility.h> #include <folly/coro/safe/AsyncClosure-fwd.h>@@ -23,13 +24,13 @@ // `#undef`ed at end-of-file not to leak this macro. #include <folly/coro/safe/detail/DefineMovableDeepConstLrefCopyable.h> #include <folly/detail/tuple.h>-#include <folly/lang/Assume.h>-#include <folly/lang/Bindings.h>-#include <folly/lang/named/Bindings.h> /// /// Please read the user- and developer-facing docs in `Capture.md`. ///+/// Callers typically only include `BindCaptures.h`, while callees need to+/// include `Captures.h`. Both are provided by `AsyncClosure.h`.+/// #if FOLLY_HAS_IMMOVABLE_COROUTINES @@ -40,13 +41,10 @@ namespace folly::coro { -// Re-export `bound_args` since it's required to use async closures & objects.-using ::folly::bindings::bound_args;- class AsyncObjectTag; template <safe_alias, typename>-class SafeTask;+class safe_task; namespace detail { @@ -96,127 +94,12 @@ !std::is_copy_assignable_v<T> && !std::is_move_constructible_v<T> && !std::is_move_assignable_v<T> && !std::swappable<T>; -// Any binding with this key is meant to be owned by the async closure-enum class capture_kind {- plain = 0,- // Syntax sugar: Passing `as_capture_indirect()` with a pointer-like (e.g.- // `unique_ptr<T>`), this emits a `capture_indirect<>`, giving access to- // the underlying `T` with just one dereference `*` / `->`, instead of 2.- indirect,-};--struct capture_bind_info_t : folly::bindings::ext::bind_info_t {- capture_kind captureKind_;-- constexpr explicit capture_bind_info_t(- // Using a constraint prevents object slicing- std::same_as<folly::bindings::ext::bind_info_t> auto bi,- capture_kind ap)- : folly::bindings::ext::bind_info_t(std::move(bi)), captureKind_(ap) {}-};--template <capture_kind Kind, typename UpdateBI = std::identity>-struct as_capture_bind_info {- // Using `auto` prevents object slicing- constexpr auto operator()(auto bi) {- return capture_bind_info_t{UpdateBI{}(std::move(bi)), Kind};- }-};- template <typename, template <typename> class, typename> class capture_crtp_base; } // namespace detail -///-/// `as_capture()` and `as_capture_indirect()` work much like other-/// `folly::bindings` modifiers. However, since they're primarily intended-/// for `async_closure` arguments, you will practically only use them:-/// - alone, for non-`co_cleanup` arguments;-/// - with `make_in_place()` or `make_in_place_with()`, for `co_cleanup`-/// arguments;-/// - with `constant()`, for either.-///-/// `capture_in_place<T>()` is short for `as_capture(make_in_place<T>())`.-///-/// See `Captures.md` and `folly/lang/Bindings.md`.-///--template <typename... Ts>-struct as_capture- : ::folly::bindings::ext::merge_update_bound_args<- detail::as_capture_bind_info<detail::capture_kind::plain>,- Ts...> {- using ::folly::bindings::ext::merge_update_bound_args<- detail::as_capture_bind_info<detail::capture_kind::plain>,- Ts...>::merge_update_bound_args;-};-template <typename... Ts>-as_capture(Ts&&...)- -> as_capture<folly::bindings::ext::deduce_bound_args_t<Ts>...>;--template <typename... Ts>-struct as_capture_indirect- : ::folly::bindings::ext::merge_update_bound_args<- detail::as_capture_bind_info<detail::capture_kind::indirect>,- Ts...> {- using ::folly::bindings::ext::merge_update_bound_args<- detail::as_capture_bind_info<detail::capture_kind::indirect>,- Ts...>::merge_update_bound_args;-};-template <typename... Ts>-as_capture_indirect(Ts&&...)- -> as_capture_indirect<folly::bindings::ext::deduce_bound_args_t<Ts>...>;--// Sugar for `as_capture{const_ref{...}}`-template <typename... Ts>-struct capture_const_ref- : ::folly::bindings::ext::merge_update_bound_args<- detail::as_capture_bind_info<- detail::capture_kind::plain,- ::folly::bindings::detail::const_ref_bind_info>,- Ts...> {- using ::folly::bindings::ext::merge_update_bound_args<- detail::as_capture_bind_info<- detail::capture_kind::plain,- ::folly::bindings::detail::const_ref_bind_info>,- Ts...>::merge_update_bound_args;-};-template <typename... Ts>-capture_const_ref(Ts&&...)- -> capture_const_ref<folly::bindings::ext::deduce_bound_args_t<Ts>...>;-// Sugar for `as_capture{mut_ref{...}}`-template <typename... Ts>-struct capture_mut_ref- : ::folly::bindings::ext::merge_update_bound_args<- detail::as_capture_bind_info<- detail::capture_kind::plain,- ::folly::bindings::detail::mut_ref_bind_info>,- Ts...> {- using ::folly::bindings::ext::merge_update_bound_args<- detail::as_capture_bind_info<- detail::capture_kind::plain,- ::folly::bindings::detail::mut_ref_bind_info>,- Ts...>::merge_update_bound_args;-};-template <typename... Ts>-capture_mut_ref(Ts&&...)- -> capture_mut_ref<folly::bindings::ext::deduce_bound_args_t<Ts>...>;--// Sugar for `as_capture{make_in_place<T>(...)}` template <typename T>-auto capture_in_place(auto&&... as [[clang::lifetimebound]]) {- return as_capture(- ::folly::bindings::make_in_place<T>(static_cast<decltype(as)>(as)...));-}-// Sugar for `as_capture{make_in_place_with(fn, ...)}`-auto capture_in_place_with(- auto make_fn, auto&&... as [[clang::lifetimebound]]) {- return as_capture(::folly::bindings::make_in_place_with(- std::move(make_fn), static_cast<decltype(as)>(as)...));-}--template <typename T> requires(!detail::has_async_closure_co_cleanup<T>) class capture; template <typename T>@@ -272,17 +155,12 @@ namespace detail { -class capture_private_t {- protected:- friend struct CapturesTest;- template <typename, template <typename> class, typename>- friend class capture_crtp_base;- template <typename, auto, size_t>- friend class capture_binding_helper;- template <auto>- friend auto bind_captures_to_closure(auto&&, auto);- friend constexpr capture_private_t coro_safe_detail_bindings_test_private();- friend class ::folly::coro::AsyncObjectTag;+// DANGER: Using this passkey makes it easy to break the lifetime-safety+// guarantees of `folly/coro/safe`, so before adding a new callsite, get+// familiar with the lifetime-safety docs, and get a careful review. This used+// to have a private-with-friends constructor, but the friend list grew+// unmanageably large as the number of lifetime-safe utilities increased.+struct capture_private_t { explicit capture_private_t() = default; }; @@ -329,7 +207,7 @@ return fn(); } - // Object intended for use with `capture` (like `SafeAsyncScope`) may+ // Object intended for use with `capture` (like `safe_async_scope`) may // provide overloads of the helper function `capture_proxy` to provide // proxy types for `capture` operators `*` and `->`. //@@ -345,7 +223,7 @@ // `co_cleanup_capture<...AsyncScope...>` that was originally NOT // restricted -- so, "restricted" is a property of the reference, not // of the underlying scope object.- // - Therefore, the public API of `SafeAsyncScope` must sit in a+ // - Therefore, the public API of `safe_async_scope` must sit in a // "reference" object that knows if it's restricted, not in the storage // object (which does not). // - It would break encapsulation to put `AsyncScope`-specific logic like@@ -372,7 +250,13 @@ } else if constexpr (Kind == ext::capture_proxy_kind::lval_ref) { return lref; // Unproxied l-value reference } else if constexpr (Kind == ext::capture_proxy_kind::rval_ref) {- return std::move(lref); // Unproxied r-value reference+ // Implement regular forwarding-ref semantics:+ // (V&)&& -> V&, (V)&& -> V&&, (V&&)&& -> V&&+ if constexpr (std::is_lvalue_reference_v<T>) {+ return lref;+ } else {+ return std::move(lref);+ } } else if constexpr ( Kind == ext::capture_proxy_kind::lval_ptr || Kind == ext::capture_proxy_kind::rval_ptr) {@@ -517,7 +401,7 @@ // of your closure. That deliberately has stricter single-use semantics // than `V&&` in vanilla C++ -- for example, without single-use, an rref // could be used to move out a value that is still referenced in- // SafeAsyncScope task. Having the explicit && -> & conversion permits+ // safe_async_scope task. Having the explicit && -> & conversion permits // the child change its mind about moving out the value. // // Future ideas & implementation notes:@@ -701,7 +585,7 @@ // dereference operations into one for better UX. There is no need for a // `capture_heap_indirect_storage`, since this "indirect" syntax sugar only // applies to pointer types, which are always cheaply movable, and thus-// don't benefit from `make_in_place`.+// don't benefit from `bind::in_place`. // // Similarly, no support for `co_cleanup()` captures since those generally // aren't pointer-like, and won't suffer from double-dereferences.@@ -728,18 +612,18 @@ std::move(*this).capture_storage<Derived, RefArgT, T>::operator*()); } [[nodiscard]] constexpr decltype(auto) operator->() & noexcept {- return (capture_storage<Derived, RefArgT, T>::operator->())->operator->();+ return (capture_storage<Derived, RefArgT, T>::operator->()) -> operator->(); } [[nodiscard]] constexpr decltype(auto) operator->() const& noexcept {- return (capture_storage<Derived, RefArgT, T>::operator->())->operator->();+ return (capture_storage<Derived, RefArgT, T>::operator->()) -> operator->(); } [[nodiscard]] constexpr decltype(auto) operator->() && noexcept { return (std::move(*this).capture_storage<Derived, RefArgT, T>::operator->())- ->operator->();+ -> operator->(); } [[nodiscard]] constexpr decltype(auto) operator->() const&& noexcept { return (std::move(*this).capture_storage<Derived, RefArgT, T>::operator->())- ->operator->();+ -> operator->(); } // Unlike other captures, `capture_indirect` is nullable since the@@ -769,6 +653,41 @@ } }; +// `capture_safety_impl_v` is separate for `AsyncObject.h` to specialize+template <typename T, safe_alias Default>+inline constexpr auto capture_safety_impl_v = safe_alias_of<T, Default>::value;++// ALL "capture" types must have `folly_private_safe_alias_t` markings.+//+// `capture` refs are only valid as long as their on-closure storage. They can+// be copied/moved, so their `safe_alias` marking is the only thing preventing+// the use of invalid references. The docs in `enum class safe_alias` discuss+// how safety levels are assigned for closure `capture`s. `async_closure`+// invokes `to_capture_ref()` to emit refs with the appropriate safety.+//+// If the underlying type is `<= shared_cleanup`, that leaks through to+// all `capture`s containing it. See e.g. `AsyncObjectPtr`.+// * Note: A `shared_cleanup` type `T` gives a closure a way of passing refs+// onto parent `safe_async_scope`s (generically: cleanup phases), so+// `capture<T>` must never be safer than `T` (unless we're dealing with a+// restricted capture ref),+//+// Otherwise, the safety measurement of `T` is "outer" to the current+// closure, and is one of `after_cleanup_ref`, `co_cleanup_safe_ref`, or+// `maybe_value`. Those should all behave the same inside the closure,+// so `MaxRefSafety` is all that matters.+// * Note: `capture<V>` is convertible to `capture<V&>` etc, so the ref+// version should never be safer.+template <typename T, safe_alias MaxRefSafety, safe_alias Default>+struct capture_safety+ : safe_alias_constant<+ (capture_safety_impl_v<std::remove_reference_t<T>, Default> <=+ safe_alias::shared_cleanup)+ ? std::min(+ MaxRefSafety,+ capture_safety_impl_v<std::remove_reference_t<T>, Default>)+ : MaxRefSafety> {};+ } // namespace detail // Please read the file docblock.@@ -794,6 +713,9 @@ public: FOLLY_MOVABLE_AND_DEEP_CONST_LREF_COPYABLE(capture, T); using detail::capture_storage<capture<T>, capture, T>::capture_storage;+ template <safe_alias Default>+ using folly_private_safe_alias_t =+ detail::capture_safety<T, safe_alias::co_cleanup_safe_ref, Default>; }; template <typename T> // may be a value or reference requires(!detail::has_async_closure_co_cleanup<T>)@@ -806,14 +728,17 @@ after_cleanup_capture<T>, after_cleanup_capture, T>::capture_storage;+ template <safe_alias Default>+ using folly_private_safe_alias_t =+ detail::capture_safety<T, safe_alias::after_cleanup_ref, Default>; }; // The use-case for `capture_heap` is to allow a closure without cleanup // args to avoid an inner/outer task split, while still taking-// `make_in_place` arguments. This is meant to be an implementation detail+// `bind::in_place` arguments. This is meant to be an implementation detail // that's almost fully API-compatible with `capture`. At a future // point we *could* remove this:-// - Then, any use of `make_in_place` would auto-create an outer task.+// - Then, any use of `bind::in_place` would auto-create an outer task. // - Any user code that explicitly specifies `capture_heap` in signatures // would need to be updated to `capture`. // - Any places that rely on moving `capture_heap<V>` would need to migrate@@ -826,6 +751,9 @@ public: using detail::capture_heap_storage<capture_heap<T>, capture, T>:: capture_heap_storage;+ template <safe_alias Default>+ using folly_private_safe_alias_t =+ detail::capture_safety<T, safe_alias::co_cleanup_safe_ref, Default>; }; template <typename T> class after_cleanup_capture_heap@@ -838,6 +766,9 @@ after_cleanup_capture_heap<T>, after_cleanup_capture, T>::capture_heap_storage;+ template <safe_alias Default>+ using folly_private_safe_alias_t =+ detail::capture_safety<T, safe_alias::after_cleanup_ref, Default>; }; // `capture_indirect<SomePtr<T>>` is like `capture<SomePtr<T>>` with syntax@@ -852,6 +783,9 @@ capture_indirect<T>, capture_indirect, T>::capture_indirect_storage;+ template <safe_alias Default>+ using folly_private_safe_alias_t =+ detail::capture_safety<T, safe_alias::co_cleanup_safe_ref, Default>; }; template <typename T> class after_cleanup_capture_indirect@@ -864,6 +798,9 @@ after_cleanup_capture_indirect<T>, after_cleanup_capture_indirect, T>::capture_indirect_storage;+ template <safe_alias Default>+ using folly_private_safe_alias_t =+ detail::capture_safety<T, safe_alias::after_cleanup_ref, Default>; }; // A closure that takes a cleanup arg is required to mark its directly-owned@@ -887,6 +824,9 @@ FOLLY_MOVABLE_AND_DEEP_CONST_LREF_COPYABLE(co_cleanup_capture, T); using detail::capture_storage<co_cleanup_capture<T>, co_cleanup_capture, T>:: capture_storage;+ template <safe_alias Default>+ using folly_private_safe_alias_t =+ detail::capture_safety<T, safe_alias::shared_cleanup, Default>; }; // What this accomplishes, in brief -- details in `Captures.md`:@@ -916,6 +856,12 @@ restricted_co_cleanup_capture<T>, restricted_co_cleanup_capture, T>::capture_storage;+ // FIXME: `capture_safety` will still measure this as `shared_cleanup` due+ // to `T` being that safety. So, when implementing restricted refs, we'll+ // have to add a new case to `capture_safety` to handle this.+ template <safe_alias Default>+ using folly_private_safe_alias_t =+ detail::capture_safety<T, safe_alias::after_cleanup_ref, Default>; }; namespace detail {@@ -940,99 +886,22 @@ concept is_any_capture_val = is_any_capture<T> && !std::is_reference_v<typename T::capture_type>; -// `capture_safety_impl_v` is separate for `AsyncObject.h` to specialize-template <typename T>-inline constexpr auto capture_safety_impl_v = safe_alias_of_v<T>;-// If the underlying type is `<= shared_cleanup`, that leaks through to-// all `capture`s containing it. See e.g. `AsyncObjectPtr`.-// * Note: A `shared_cleanup` type `T` gives a closure a way of passing refs-// onto parent `SafeAsyncScope`s (generically: cleanup phases), so-// `capture<T>` must never be safer than `T` (unless we're dealing with a-// restricted capture ref),-//-// Otherwise, the safety measurement of `T` is "outer" to the current-// closure, and is one of `after_cleanup_ref`, `co_cleanup_safe_ref`, or-// `maybe_value`. Those should all behave the same inside the closure,-// so `MaxRefSafety` is all that matters.-// * Note: `capture<V>` is convertible to `capture<V&>` etc, so the ref-// version should never be safer.-template <typename T, safe_alias MaxRefSafety>-struct capture_safety- : safe_alias_constant<- (capture_safety_impl_v<std::remove_reference_t<T>> <=- safe_alias::shared_cleanup)- ? std::min(- MaxRefSafety,- capture_safety_impl_v<std::remove_reference_t<T>>)- : MaxRefSafety> {};- } // namespace detail } // namespace folly::coro -namespace folly {--// Set `safe_alias` values for all the `capture` types.-//-// `capture` refs are only valid as long as their on-closure storage. They-// can be copied/moved, so their `safe_alias` marking is the only thing-// preventing the use of invalid references. The docs in `enum class-// safe_alias` discuss how safety levels are assigned for closure-// `capture`s. `async_closure` invokes `to_capture_ref()` to emit refs with-// the appropriate safety.--template <typename T>-struct safe_alias_of<::folly::coro::capture<T>>- : folly::coro::detail::capture_safety<T, safe_alias::co_cleanup_safe_ref> {-};-template <typename T>-struct safe_alias_of<::folly::coro::capture_heap<T>>- : folly::coro::detail::capture_safety<T, safe_alias::co_cleanup_safe_ref> {-};-template <typename T>-struct safe_alias_of<::folly::coro::capture_indirect<T>>- : folly::coro::detail::capture_safety<T, safe_alias::co_cleanup_safe_ref> {-};--template <typename T>-struct safe_alias_of<::folly::coro::after_cleanup_capture<T>>- : folly::coro::detail::capture_safety<T, safe_alias::after_cleanup_ref> {};-template <typename T>-struct safe_alias_of<::folly::coro::after_cleanup_capture_heap<T>>- : folly::coro::detail::capture_safety<T, safe_alias::after_cleanup_ref> {};-template <typename T>-struct safe_alias_of<::folly::coro::after_cleanup_capture_indirect<T>>- : folly::coro::detail::capture_safety<T, safe_alias::after_cleanup_ref> {};--template <typename T>-struct safe_alias_of<::folly::coro::co_cleanup_capture<T>>- : folly::coro::detail::capture_safety<T, safe_alias::shared_cleanup> {};-// FIXME: `capture_safety` will still measure this as `shared_cleanup` due-// to `T` being that safety. So, when implementing restricted refs, we'll-// have to add a new case to `capture_safety` to handle this.-template <typename T>-struct safe_alias_of<::folly::coro::restricted_co_cleanup_capture<T>>- : folly::coro::detail::capture_safety<T, safe_alias::after_cleanup_ref> {};--} // namespace folly--// We extended `folly::bindings` with `capture_kind`, so we must explicitly-// specialize `binding_policy`. We reuse the standard rules. Custom-// `capture` binding logic is in `async_closure_bindings()`.-namespace folly::bindings::ext {-template <auto BI, typename BindingType>- requires std::same_as< // Written as a constraint to prevent object slicing- decltype(BI),- ::folly::coro::detail::capture_bind_info_t>-class binding_policy<ext::binding_t<BI, BindingType>> {- private:- using standard = binding_policy<ext::binding_t<bind_info_t{BI}, BindingType>>;-- public:- using storage_type = typename standard::storage_type;- using signature_type = typename standard::signature_type;+// Customize `safe_closure` to store `capture<V>` as `capture<V&>`.+namespace folly::detail {+template <typename ST>+struct safe_closure_arg_storage_helper;+template <coro::detail::is_any_capture_val ST>+struct safe_closure_arg_storage_helper<ST> {+ // We should never move `capture<Val>`s, so `safe_closure` will fail to+ // implicitly convert from `capture<Val>&&` to `capture<V&>` since the+ // `&&`-qualified conversions are `explicit` above.+ using type = coro::capture_ref_conversion_t<ST&>; };-} // namespace folly::bindings::ext+} // namespace folly::detail #endif
@@ -21,20 +21,28 @@ #if FOLLY_HAS_IMMOVABLE_COROUTINES -/// `NowTask<T>` quacks like `Task<T>` but is immovable, and must be+/// `now_task<T>` quacks like `Task<T>` but is immovable, and must be /// `co_await`ed in the same expression that created it. ///-/// Using `NowTask` by default brings considerable safety benefits. With+/// Using `now_task` by default brings considerable safety benefits. With /// `Task`, the following would be anti-patterns that cause dangling reference-/// bugs, but with `NowTask`, C++ lifetime extension rules ensure that they+/// bugs, but with `now_task`, C++ lifetime extension rules ensure that they /// simply work. /// - Pass-by-reference into coroutines. /// - Ephemeral coro lambdas with captures. /// - Coro lambdas with capture-by-reference. ///+/// Q: Is it `now_task` or `NowTask`?+///+/// A: In order to ease adoption, both forms will compile, but `now_task` is+/// primary. Both are intended to eventually be renamed to simply+/// `folly::coro::task`, while the current movable, delayed-awaitable task+/// will be renamed to `unsafe_task`, and largely superseded by+/// `async_closure` and various `safe_task` flavors.+/// /// Notes:-/// - (subject to change) Unlike `SafeTask`, `NowTask` does NOT check-/// `safe_alias_of` for the return type `T`. `NowTask` is essentially an+/// - (subject to change) Unlike `safe_task`, `now_task` does NOT check+/// `safe_alias_of` for the return type `T`. `now_task` is essentially an /// immediate async function -- it satisfies the structured concurrency /// maxim of "lexical scope drives both control flow & lifetime". That /// lowers the odds that returned pointers/references are unexpectedly@@ -50,105 +58,134 @@ class BackgroundTask; template <typename T = void>-class NowTask;+class now_task; template <typename T = void>-class NowTaskWithExecutor;+class now_task_with_executor; +// Backwards-compatibility shims+template <typename T = void>+using NowTask = now_task<T>;+template <typename T = void>+using NowTaskWithExecutor = now_task_with_executor<T>;+ namespace detail { template <typename T>-struct NowTaskWithExecutorCfg : DoesNotWrapAwaitable {+struct now_task_with_executor_cfg : DoesNotWrapAwaitable { using InnerTaskWithExecutorT = TaskWithExecutor<T>;- using WrapperTaskT = NowTask<T>;+ using WrapperTaskT = now_task<T>; }; template <typename T>-using NowTaskWithExecutorBase =- AddMustAwaitImmediately<TaskWithExecutorWrapperCrtp<- NowTaskWithExecutor<T>,- detail::NowTaskWithExecutorCfg<T>>>;+using now_task_with_executor_base =+ ext::wrap_must_use_immediately_t<TaskWithExecutorWrapperCrtp<+ now_task_with_executor<T>,+ detail::now_task_with_executor_cfg<T>>>; } // namespace detail template <typename T>-class FOLLY_NODISCARD NowTaskWithExecutor final- : public detail::NowTaskWithExecutorBase<T> {+class [[nodiscard]]+now_task_with_executor final : public detail::now_task_with_executor_base<T> { protected:- using detail::NowTaskWithExecutorBase<T>::NowTaskWithExecutorBase;+ using detail::now_task_with_executor_base<T>::now_task_with_executor_base; template <safe_alias, typename> friend class BackgroundTask; // for `unwrapTaskWithExecutor`, remove later+ public:+ [[deprecated(+ "`as_unsafe()` is provided as an escape hatch for interoperating with "+ "older futures-based code, or other places not yet compatible with "+ "true structured concurrency patterns. Beware, the full `Task` API "+ "abounds with footguns like `start()` and `semi()` -- including UB, "+ "leaks, and lost errors. See `folly/coro/safe/docs/AsUnsafe.md` for "+ " safe migration patterns.")]]+ TaskWithExecutor<T> as_unsafe() && {+ return std::move(*this).unwrapTaskWithExecutor();+ } }; namespace detail { template <typename T>-class NowTaskPromise final- : public TaskPromiseWrapper<T, NowTask<T>, TaskPromise<T>> {};+class now_task_promise final+ : public TaskPromiseWrapper<T, now_task<T>, TaskPromise<T>> {}; template <typename T>-struct NowTaskCfg : DoesNotWrapAwaitable {+struct now_task_cfg : DoesNotWrapAwaitable { using ValueT = T; using InnerTaskT = Task<T>;- using TaskWithExecutorT = NowTaskWithExecutor<T>;- using PromiseT = NowTaskPromise<T>;+ using TaskWithExecutorT = now_task_with_executor<T>;+ using PromiseT = now_task_promise<T>; }; template <typename T>-using NowTaskBase =- AddMustAwaitImmediately<TaskWrapperCrtp<NowTask<T>, detail::NowTaskCfg<T>>>;+using now_task_base = ext::wrap_must_use_immediately_t<+ TaskWrapperCrtp<now_task<T>, detail::now_task_cfg<T>>>; } // namespace detail template <safe_alias, typename>-class SafeTask;+class safe_task; template <safe_alias S, typename U>-auto toNowTask(SafeTask<S, U>);+auto to_now_task(safe_task<S, U> t); template <typename T>-class FOLLY_CORO_TASK_ATTRS NowTask final : public detail::NowTaskBase<T> {+class FOLLY_CORO_TASK_ATTRS now_task final : public detail::now_task_base<T> { protected:- using detail::NowTaskBase<T>::NowTaskBase;+ using detail::now_task_base<T>::now_task_base; template <typename U> // can construct- friend auto toNowTask(Task<U>);+ friend auto to_now_task(Task<U>); template <safe_alias S, typename U> // can construct- friend auto toNowTask(SafeTask<S, U>);+ friend auto to_now_task(safe_task<S, U>); template <typename U> // can construct & `unwrapTask`- friend auto toNowTask(NowTask<U>);+ friend auto to_now_task(now_task<U>);++ public:+ [[deprecated(+ "`as_unsafe()` is provided as an escape hatch for interoperating with "+ "older futures-based code, or other places not yet compatible with "+ "true structured concurrency patterns. Beware, the full `Task` API "+ "abounds with footguns like `start()` and `semi()` -- including UB, "+ "leaks, and lost errors. See `folly/coro/safe/docs/AsUnsafe.md` for "+ " safe migration patterns.")]]+ Task<T> as_unsafe() && {+ return std::move(*this).unwrapTask();+ } }; -// NB: `toNowTask(SafeTask)` is in `SafeTask.h` to avoid circular deps.+// NB: `to_now_task(safe_task)` is in `SafeTask.h` to avoid circular deps. template <typename T>-auto toNowTask(Task<T> t) {- return NowTask<T>{std::move(t)};+auto to_now_task(Task<T> t) {+ return now_task<T>{std::move(t)}; } template <typename T>-auto toNowTask(NowTask<T> t) {- return NowTask<T>{std::move(t).unwrapTask()};+auto to_now_task(now_task<T> t) {+ return now_task<T>{std::move(t).unwrapTask()}; } // Apparently, Clang 15 has a bug in prvalue semantics support, so it cannot // return immovable coroutines. #if !defined(__clang__) || __clang_major__ > 15 -/// Make a `NowTask` that trivially returns a value.+/// Make a `now_task` that trivially returns a value. template <class T>-NowTask<T> makeNowTask(T t) {+now_task<T> make_now_task(T t) { co_return t; } -/// Make a `NowTask` that trivially returns no value-inline NowTask<> makeNowTask() {+/// Make a `now_task` that trivially returns no value+inline now_task<> make_now_task() { co_return; }-/// Same as makeNowTask(). See Unit-inline NowTask<> makeNowTask(Unit) {+/// Same as make_now_task(). See Unit+inline now_task<> make_now_task(Unit /*unused*/) { co_return; } -/// Make a `NowTask` that will trivially yield an exception.+/// Make a `now_task` that will trivially yield an exception. template <class T>-NowTask<T> makeErrorNowTask(exception_wrapper ew) {+now_task<T> make_error_now_task(exception_wrapper ew) { co_yield co_error(std::move(ew)); } -#endif // no `makeNowTask` on old/buggy clang+#endif // no `make_now_task` on old/buggy clang } // namespace folly::coro
@@ -1,203 +0,0 @@-/*- * Copyright (c) Meta Platforms, Inc. and affiliates.- *- * Licensed under the Apache License, Version 2.0 (the "License");- * you may not use this file except in compliance with the License.- * You may obtain a copy of the License at- *- * http://www.apache.org/licenses/LICENSE-2.0- *- * Unless required by applicable law or agreed to in writing, software- * distributed under the License is distributed on an "AS IS" BASIS,- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.- * See the License for the specific language governing permissions and- * limitations under the License.- */--#pragma once--#include <folly/Traits.h>-#include <folly/lang/SafeAlias-fwd.h>--#include <type_traits>--namespace folly {-template <typename> // Forward-decl to keep `RValueReferenceWrapper.h` dep-free-class rvalue_reference_wrapper;-} // namespace folly--/*-"Aliasing" is indirect access to memory via pointers or references. It is-the major cause of memory-safety bugs in C++, but is also essential for-writing correct & performant C++ programs. Fortunately,- - Much business logic can be written in a pure-functional style, where- only value semantics are allowed. Such code is easier to understand,- and has much better memory-safety.- - When references ARE used, the most common scenario is passing a- reference from a parent lexical scope to descendant scopes.--`safe_alias_of_v` is a _heuristic_ to check whether a type is likely to be-memory-safe in the above settings. The `safe_alias` enum shows a hierarchy-of memory safety, but you only need to know about two:- - `unsafe` -- e.g. raw pointers or references, and- - `maybe_value` -- `int`, `std::pair<int, char>`, or `std::unique_ptr<Foo>`.--A user can easily bypass the heuristic -- since C++ lacks full reflection,-it is impossible to make this bulletproof. Our goals are much more modest:- - Make unsafe aliasing **more** visible in code review, and- - Encourage programmers to use safe semantics by default.--The BIG CAVEATS are:-- - The "composition hole" -- i.e. aliasing hidden in structures. We can't see- unsafe class members, so `UnsafeStruct` below will be deduced to have- `maybe_value` safety unless you specialize `safe_alias_of<UnsafeStruct>`.- struct UnsafeStruct { int* rawPtr; };- Future: Perhaps with C++26 reflection, this could be fixed.-- The "lambda hole" is a particularly easy instance of the "composition hole".- With lambda captures, a parent needs just one `&` to let a child pass a- soon-to-be-dangling reference up the stack. E.g. this compiles:- int* badPtr;- auto t = async_closure(- // LAMBDA HOLE: We can't tell this callable object is unsafe!- bound_args{[&](int p) { *badPtr = p; }},- [](auto fn) -> ClosureTask<void> {- int i = 5;- fn(i); // FAILURE: Dereferencing uninitialized `badPtr`.- co_return;- });-- - Nullability & pointer stability: These hazards are not very specific to- coroutines, and the current design of `folly/coro/safe` largely avoids- unstable containers. Nonetheless, you must beware container mutation is- an easy way to invalidate `safe_alias` memory-safety measurements. For- example `unique_ptr<int>` and `vector<int>` have `maybe_value` safety.- However, if you mutate them (`reset()`, `clear()`, etc), that would- invalidate any async references (e.g. `Captures.h`) pointing inside.- Luckily, there's no implicit way of getting a safe reference to inside- regular containers. However, it is recommended to reduce accidental- nullability where possible. For example, `capture<unique_ptr<T>>`- exposes `reset()`, but `capture_indirect<unique_ptr<T>>` hides it behind- `get_underlying_unsafe()`. Better yet, `capture<AsyncObjectPtr<T>>`- blocks the underlying `clear()` method entirely.--If you need to bypass this control, prefer the `manual_safe_*` wrappers-below, instead of writing a custom workaround. Always explain why it's safe.--To teach `safe_alias_of` about your type, include `SafeAlias-fwd.h` and either:- 1) Add a member type alias to your class:- using using folly_private_safe_alias_t = safe_alias_constant<...>;- 2) Specialize `folly::safe_alias_of<YourT>`.--When adding `safe_alias` annotations to types, stick to these principles:- - Always mark the `safe_alias` level in the header that declares your type.- For `std` types you cannot change, add the specialization here, in- `SafeAlias.h`. Since we cannot forward-declare from `std`, this- unfortunately imposes a tradeoff between build cost and safety. Commonly- used containers are worth the cost. For less-commonly used containers, we- could develop a multi-header setup, plus some linter coverage to ensure the- right headers ultimately do get included.- - Only use `maybe_value` if your type ACTUALLY follows value semantics.- - Unless you're implementing an `async_closure`-integrated type, it is VERY- unlikely that you should use anything besides `unsafe` or `maybe_value`.- - Use `safe_alias_of_pack` to aggregate safety for a multi-part type.-*/-namespace folly {--// Types are `maybe_value` unless otherwise specified. Note that-// `SafeAlias-fwd.h` already marks raw pointers & refs as `unsafe`, and peels-// off CV qualifiers from the type being tested.-//-// See also: `safe_alias_of_v`.-//-// As explained in `SafeAlias-fwd.h`, do NOT move this to the `fwd` header. To-// guarantee safety, this permissive primary template must be colocated with-// the other specializations below.-template <typename T, typename /*SFINAE*/>-struct safe_alias_of : safe_alias_constant<safe_alias::maybe_value> {};--// Reference wrappers are unsafe.-template <typename T>-struct safe_alias_of<std::reference_wrapper<T>>- : safe_alias_constant<safe_alias::unsafe> {};-template <typename T>-struct safe_alias_of<folly::rvalue_reference_wrapper<T>>- : safe_alias_constant<safe_alias::unsafe> {};--// Let `safe_alias_of_v` recursively inspect `std` containers that are likely-// to be involved in bugs. If you encounter a memory-safety issue that-// would've been caught by this, feel free to extend this.-template <typename... As>-struct safe_alias_of<std::tuple<As...>> : safe_alias_of_pack<As...> {};-template <typename... As>-struct safe_alias_of<std::pair<As...>> : safe_alias_of_pack<As...> {};-template <typename... As>-struct safe_alias_of<std::vector<As...>> : safe_alias_of_pack<As...> {};--// Recursing into `tag_t<>` type lists is nice for metaprogramming-template <typename... As>-struct safe_alias_of<::folly::tag_t<As...>> : safe_alias_of_pack<As...> {};--// IMPORTANT: If you use the `manual_safe_` escape-hatch wrappers, you MUST-// comment with clear proof of WHY your usage is safe. The goal is to-// ensure careful review of such code.-//-// Careful: With the default `Safety`, the contained value or reference can be-// passed anywhere -- the wrapper pretends to be a value type.-//-// If you know a more restrictive safety level for your ref, annotate it to-// improve safety:-// - `after_cleanup_ref` for things owned by co_cleanup args of this closure,-// - `co_cleanup_safe_ref` for refs to non-cleanup args owned by this closure,-// or any ancestor closure.-//-// The types are public since they may occur in user-facing signatures.--template <safe_alias, typename T>-struct manual_safe_ref_t : std::reference_wrapper<T> {- using typename std::reference_wrapper<T>::type;- using std::reference_wrapper<T>::reference_wrapper;-};--template <safe_alias, typename T>-struct manual_safe_val_t {- using type = T;-- template <typename... Args>- manual_safe_val_t(Args&&... args) : t_(static_cast<Args&&>(args)...) {}- template <typename Fn>- manual_safe_val_t(std::in_place_type_t<T>, Fn fn) : t_(fn()) {}-- T& get() & noexcept { return t_; }- operator T&() & noexcept { return t_; }- const T& get() const& noexcept { return t_; }- operator const T&() const& noexcept { return t_; }- T&& get() && noexcept { return std::move(t_); }- operator T&&() && noexcept { return std::move(t_); }-- private:- T t_;-};--template <safe_alias Safety = safe_alias::maybe_value, typename T = void>-auto manual_safe_ref(T& t) {- return manual_safe_ref_t<Safety, T>{t};-}-template <safe_alias Safety = safe_alias::maybe_value, typename T>-auto manual_safe_val(T t) {- return manual_safe_val_t<Safety, T>{std::move(t)};-}-template <safe_alias Safety = safe_alias::maybe_value, typename Fn>-auto manual_safe_with(Fn&& fn) {- using FnRet = decltype(static_cast<Fn&&>(fn)());- return manual_safe_val_t<Safety, FnRet>{- std::in_place_type<FnRet>, static_cast<Fn&&>(fn)};-}--template <safe_alias S, typename T>-struct safe_alias_of<manual_safe_ref_t<S, T>> : safe_alias_constant<S> {};-template <safe_alias S, typename T>-struct safe_alias_of<manual_safe_val_t<S, T>> : safe_alias_constant<S> {};--} // namespace folly
@@ -18,78 +18,88 @@ #include <folly/coro/TaskWrapper.h> #include <folly/coro/safe/NowTask.h>-#include <folly/coro/safe/SafeAlias.h>+#include <folly/lang/SafeAlias.h> #if FOLLY_HAS_IMMOVABLE_COROUTINES namespace folly::coro { -/// Why is `SafeTask.h` useful? See `SafeTask.md`.+/// Why is `SafeTask.h` useful? ///-/// Typically, you will not use `SafeTask` directly. Instead, choose one of+/// `now_task` from `NowTask.h` should be your default. This immovable task+/// should always be awaited in the full-expression that created it,+/// eliminating most classes of lifetime-safety bugs.+///+/// If you need a MOVABLE task with compile-time safety checks, read on.+///+/// Typically, you will not use `safe_task` directly. Instead, choose one of /// the type-aliases below, following `APIBestPractices.md` guidance. Briefly:-/// - `ValueTask`: Use if your coro only takes value-semantic args.-/// - `MemberTask`: Use for all non-static member functions. Can be-/// awaited immediately (like `NowTask`), or wrapped in an-/// `async_closure` to support less-structured concurrency -- including-/// scheduling on a background scope belonging to the object.-/// - `ClosureTask`: Use if your coro is called via `async_closure`.-/// - `CoCleanupSafeTask`: Use for tasks that can be directly scheduled on a-/// `SafeAsyncScope`.-/// - (not in `SafeTask.h`) `NowTask`: All other coros. This requires the-/// task to always be awaited in the expression that created it,-/// eliminating a variety of common dangling reference bugs.-/// - `AutoSafeTask`: Generic coros where you want the argument & return-/// types to automatically branch between a `NowTask` and a `SafeTask`. ///-/// `SafeTask` is a thin wrapper around `folly::coro::Task` that uses+/// `value_task`:+/// Use if your coro only takes value-semantic args.+///+/// `member_task`:+/// Use for non-static member functions. Can be awaited immediately (like+/// `now_task`), or wrapped in an `async_closure` to support less-structured+/// concurrency -- including scheduling on a background scope belonging to+/// the object. IMPORTANT: adding a `member_task` to a class requires it to+/// have an **explicit** `folly/lang/SafeAlias.h` annotation.+///+/// `closure_task`:+/// Use if your coro is called via `async_closure`. Outside of closures,+/// this behaves like `now_task`.+///+/// `co_cleanup_safe_task`:+/// Use for tasks that can be directly scheduled on a `safe_async_scope`.+///+/// `auto_safe_task`:+/// Generic coros where you want the argument & return types to automatically+/// choose between a `now_task` and a `safe_task`.+///+/// `safe_task` is a thin wrapper around `folly::coro::Task` that uses /// `safe_alias_of` to enforce some compile-time guarantees:-/// - The `SafeTask` has `safe_alias_of` memory safety at least as high as+/// - The `safe_task` has `safe_alias_of` memory safety at least as high as /// the coro's arguments. In particular, no args are taken by reference. /// - Regardless of the task's declared safety, the coro's return must-/// have safety `maybe_value` (explained in `SafeTaskRetAndArgs`).+/// have safety `maybe_value` (explained in `safe_task_ret_and_args`). /// - The coroutine is NOT a stateful callable -- this prohibits lambda /// captures, since those are a very common cause of coro memory bugs. template <safe_alias, typename = void>-class SafeTask;+class safe_task; template <safe_alias, typename = void>-class SafeTaskWithExecutor;+class safe_task_with_executor; -// A `SafeTask` whose args and return type follow value semantics.+// A `safe_task` whose args and return type follow value semantics. template <typename T = void>-using ValueTask = SafeTask<safe_alias::maybe_value, T>;+using value_task = safe_task<safe_alias::maybe_value, T>; -// A `SafeTask` that can be added to `SafeAsyncScope`, and may run during+// A `safe_task` that can be added to `safe_async_scope`, and may run during // closure cleanup. Its content must therefore be `co_cleanup_safe_ref`-safe. template <typename T = void>-using CoCleanupSafeTask = SafeTask<safe_alias::co_cleanup_safe_ref, T>;+using co_cleanup_safe_task = safe_task<safe_alias::co_cleanup_safe_ref, T>; -// Use `ClosureTask` as the inner coro type for tasks meant to ALWAYS be-// wrapped in an `async_closure`.-//-// Outside of a closure, a `ClosureTask` is immovable. If you are wanting to-// move a `ClosureTask`, construct it via an async closure, and you'll get back-// a `SafeTask` with safety measurements reflecting the safety of its args.+// Use `closure_task` as the inner coro type for tasks meant to be wrapped in+// an `async_closure` or `async_now_closure`. The closure code will measure+// the safety of its args, and return a correct `safe_task`. //-// If your use-case calls for a `SafeTask` that is sometimes wrapped in a-// closure, and sometimes is constructed without a closure, you might add a-// `MinClosureSafeTask` type alias for `closure_min_arg_safety`.+// Without a surrounding closure call, a `closure_task` is immovable, and works+// like `now_task`, but with some restrictions on its arg & return types. //-// Immovability rationale: `ClosureTask` is implemented as a `SafeTask` for+// Immovability rationale: `closure_task` is implemented as a `safe_task` for // reasons explained in the next paragraph. But, its safety contract is weaker // than that of the usual closure (it can take `capture<Val>`, which should // never be moved) -- immovability is meant to reduce the odds of misuse. // Making it truly opaque / not semi-awaitable would be a stronger safeguard, // but that requires extra complexity even just so that-// `AsNoexcept<ClosureTask<>> foo()` would compile.+// `value_or_fatal<closure_task<>> foo()` would compile. //-// "ClosureTask is a SafeTask" rationale: `async_closure` cannot emit a-// `SafeTask` without the inner coro being a `SafeTask` -- otherwise it could+// "closure_task is a safe_task" rationale: `async_closure` cannot emit a+// `safe_task` without the inner coro being a `safe_task` -- otherwise it could // not guarantee that none of the args are taken by reference. Conveniently,-// `SafeTask` also checks the return type is safe, and the coro's callable is-// stateless, so `async_closure` can skip those checks.+// `safe_task` also checks the return type is safe, and the coro's callable is+// stateless, so `async_closure` can rely on those checks. //-// This `ClosureTask` implementation uses a `safe_alias` level safer than+// This `closure_task` implementation uses a `safe_alias` level safer than // `unsafe` to get all of the above `SafeAlias` checks. The level also has // to be less safe than `shared_cleanup` so we can treat these differently: // - `capture<Value>` (safety `unsafe_closure_internal`) should stay in the@@ -100,101 +110,114 @@ // - By the way, `co_cleanup_capture<Value>` should never be moved from the // owning closure that's responsible for its cleanup. template <typename T = void>-using ClosureTask = SafeTask<safe_alias::unsafe_closure_internal, T>;+using closure_task = safe_task<safe_alias::unsafe_closure_internal, T>; -// A `MemberTask` is a hybrid of `SafeTask` and `NowTask`, intended to make-// non-static member coroutines safer.-// - It **is** a `SafeTask`, thereby forbidding `safe_alias::unsafe`-// arguments, and unsafe return types. However, since the callable of-// member coros is inherently stateful, it is special-cased to omit the-// safety checks on the implicit object parameter.-// - It is immovable like `NowTask`, which makes typical "structured-// concurrency" usage of coroutines quite safe (see `NowTask.h`).-// `MemberTask` needs this, since members take `this`, whose lifetime is-// unknown -- i.e. outside of async closure usage, a `MemberTask` is just-// a `NowTask`.+// A `member_task` is nearly identical to `closure_task`, except that it's+// usable with non-static member coroutines on **stateful** classes.+// - In `async_closure`, it behaves like `closure_task`, with one+// extra constraint -- its implicit object parameter must refer to a class+// with an explicitly annotated `safe_alias_of` (see `SafeAlias.h`).+// - Without `async_closure`, it is immediately-awaitable, like `now_task`.+// - Since it is also a `safe_task`, it forbids `safe_alias::unsafe`+// arguments, and unsafe return types. //-// For more complex usage (background tasks, async RAII), `MemberTask` has a-// special calling convention in `AsyncClosure.h`:+// To construct `async_closure`s with `member_task`s (for background or scope+// tasks), use this special calling convention: //-// async_closure(bound_args{obj, args...}, FOLLY_INVOKE_MEMBER(memberFnName))+// async_closure(bind::args{obj, args...}, FOLLY_INVOKE_MEMBER(memberFnName)) //-// Like any async closure, this safety-checks the now-explicit object param,-// and produces a movable `SafeTask` of the safety level determined from the-// arguments. This integration lets us safely schedule member coros on-// `SafeAsyncScope`, pass `co_cleanup` args into such coros, etc.+// This closure will safety-check the now-explicit object param, and produce a+// movable `safe_task` of the safety level determined by the arguments. This+// integration lets us safely schedule member coros on `safe_async_scope`, pass+// `co_cleanup` args into such coros, etc.+//+// Design note: Fundamentally, the reason that we have `member_task`, and+// cannot just use `closure_task` for class members, is that C++20 does not let+// a coroutine function distinguish (i) an "implicit object param" from (ii) a+// regular lvalue reference. For (i), `member_task` requires `async_closure`+// to invoke it with `FOLLY_INVOKE_MEMBER`, so in this case we just need the+// first arg to be a reference-to-a-safe-object. In case (ii), such as+// `closure_task`, a reference-to-a-safe-object is definitely unsafe, whereas a+// reference to a stateless/empty object is safe enough. template <typename T>-using MemberTask = SafeTask<safe_alias::unsafe_member_internal, T>;+using member_task = safe_task<safe_alias::unsafe_member_internal, T>; // NB: There are some `async_closure`-specific values of `safe_alias` that-// do not yet have a `SafeTask` alias. That's because they haven't come up+// do not yet have a `safe_task` alias. That's because they haven't come up // in user-facing type signatures. namespace detail { template <typename T, safe_alias Safety>-using AutoSafeTaskImpl = std::conditional_t<+using auto_safe_task_impl = std::conditional_t< // This checks both args & the return value because we want to avoid this- // resolving to a `SafeTask` that won't actually compile.+ // resolving to a `safe_task` that won't actually compile. (Safety >= safe_alias::closure_min_arg_safety &&- safe_alias_of_v<T> >= safe_alias::maybe_value),- SafeTask<Safety, T>,- NowTask<T>>;+ lenient_safe_alias_of_v<T> >= safe_alias::maybe_value),+ safe_task<Safety, T>,+ now_task<T>>; } -/// Coros declared as `SafeTask<Safety, T>` will satisfy the strong+/// Coros declared as `safe_task<Safety, T>` will satisfy the strong /// constraints above, or fail with a compile error. /// /// The safety of a coroutine template may vary depending on the args or /// return type, meaning that the user can't actually pick a fixed /// safety level for their generic coro. ///-/// Instead, the generic coro can return `AutoSafeTask<ReturnT,+/// Instead, the generic coro can return `auto_safe_task<ReturnT, /// SafetyArgs...>`, where `SafetyArgs` is (typically) the subset of the /// coroutine's argument types that may affect safety. ///-/// `AutoSafeTask` has a Significant Caveat -- you can't use it with+/// `auto_safe_task` has a Significant Caveat -- you can't use it with /// non-`static` member functions -- the implicit object parameter is unsafe /// (as it should be). And if you do use it, you will get a compile-time-/// error instead of a `NowTask`, simply because this type-function has no+/// error instead of a `now_task`, simply because this type-function has no /// access to the callable. See `APIBestPractices.md` for workarounds. template <typename T, typename... SafetyArgs>-using AutoSafeTask =- detail::AutoSafeTaskImpl<T, safe_alias_of_pack<SafetyArgs...>::value>;+using auto_safe_task = detail::auto_safe_task_impl<+ T,+ // Same logic as `lenient_safe_alias_of_v`+ safe_alias_of_pack<safe_alias::maybe_value, SafetyArgs...>::value>; namespace detail { struct SafeTaskTest; template <safe_alias ArgSafety, typename RetT, typename... Args>-concept SafeTaskRetAndArgs = ((safe_alias_of_v<Args> >= ArgSafety) && ...) &&+concept safe_task_ret_and_args =+ ((lenient_safe_alias_of_v<Args> >= ArgSafety) && ...) && // In the event that you need a child scope to return a reference to // something owned by a still-valid ancestor scope, we don't have a good // way to detect this automatically. To work around, use a `manual_safe_*` // wrapper in `SafeAlias.h`, and comment why it is safe.- (safe_alias_of_v<RetT> >= safe_alias::maybe_value);+ (lenient_safe_alias_of_v<RetT> >= safe_alias::maybe_value); template <typename T> concept is_stateless_class_or_func =- (std::is_class_v<T> && std::is_empty_v<T>) ||+ // `require_sizeof` avoids `is_empty` UB on incomplete types+ (require_sizeof<T> >= 0 && std::is_empty_v<T>) || (std::is_pointer_v<T> && std::is_function_v<std::remove_pointer_t<T>>); template <safe_alias, typename...>-inline constexpr bool IsSafeTaskValid = false;+inline constexpr bool is_safe_task_valid = false; // Coros taking 0 args can't be methods (no implicit object parameter), // so their safety is determined by the return type. template <safe_alias ArgSafety, typename RetT>-inline constexpr bool IsSafeTaskValid<ArgSafety, RetT> =- SafeTaskRetAndArgs<ArgSafety, RetT>;+inline constexpr bool is_safe_task_valid<ArgSafety, RetT> =+ safe_task_ret_and_args<ArgSafety, RetT>; // Inspect the first argument, which can be an implicit object parameter, to-// allow stateless callables (like lambdas), but to prohibit stateful-// callables (these can contain unsafe aliasing in their state, which we-// can't inspect). If you need to make `SafeTask`s from a stateful object,-// pass `capture<Ref>` to a static func, and check out `AsyncObject.h`.+// allow stateless callables (like lambdas), but to prohibit stateful callables+// (these can contain unsafe aliasing in their state, which we can't inspect+// automatically). If you need to make `safe_task`s from a stateful object,+// ideas include:+// - Pass a `capture<YourClass&>` to a static member `YourClass::func`+// - Explicitly mark `safe_alias_of<YourClass>` and use `member_task`.+// - Future: Check out `AsyncObject.h`. // // How this works: With >= 1 args in the pack, the `First` argument // **could** be an implicit object parameter. We don't know if it is, but // we do know that any such parameter has type lvalue reference, which means-// that it would fail `SafeTaskRetAndArgs<RetT, First, Args...>`.+// that it would fail `safe_task_ret_and_args<RetT, First, Args...>`. // // This test accepts any `First` that is an lref to a stateless class // or function -- that is, it returns `true` if the first arg is either:@@ -204,57 +227,85 @@ // pass a stateless class by reference, if it's the first param. This // should be harmless in practice. //-// FIXME: It should (?) be fine to simplify this scenario by having-// `SafeAlias` mark as "safe" all references-to-empty-classes, and all-// function pointers. Then, only a shortened comment would survive.-//-// For `MemberTask`, `First` is assumed to be the implicit object parameter.-// This cannot be safe, so we don't check it, and instead rely on-// `MemberTask`'s usage restrictions (see also `SafeTaskBaseTrait`).+// For `member_task`, `First` is assumed to be the implicit object parameter.+// We cannot deduce its safety, so we use `strict_safe_alias_of_v` to force the+// user to explicitly mark this class -- incidentally, this precludes+// `member_task` lambdas. It is safe for `member_task` to treat+// a ref-to-a-safe-object as safe, since it is usable only:+// - As a free coro, where it acts like a restricted `now_task`+// - Via `async_closure`, which enforces a `FOLLY_INVOKE_MEMBER` calling+// convention that either takes a safe capture-ref, or makes the closure+// take ownership of the closure.+// We MUST NOT apply the same treatment to `closure_task`, since it cannot+// ensure that the ref itself is safe (even if the referenced type is). template <safe_alias ArgSafety, typename RetT, typename First, typename... Args>-inline constexpr bool IsSafeTaskValid<ArgSafety, RetT, First, Args...> =- ((ArgSafety == safe_alias::unsafe_member_internal) ||- (std::is_lvalue_reference_v<First> &&+inline constexpr bool is_safe_task_valid<ArgSafety, RetT, First, Args...> =+ (std::is_lvalue_reference_v<First> &&+ ((ArgSafety == safe_alias::unsafe_member_internal &&+ strict_safe_alias_of_v<std::remove_reference_t<First>> >=+ // This `std::max` requires the implicit object parameter of+ // `member_task`s to have safety `> shared_cleanup`. In effect,+ // this prohibits `member_task`s from being added to classes that+ // store `co_cleanup_capture` refs, or (equivalently) to+ // `async_object`s. This tradeoff is required so that+ // `async_now_closure()` of `member_task`s is not ALWAYS forced to+ // apply shared-cleanup downgrades to its arguments. The details+ // are in `async_closure_safeties_and_bindings`. If you find+ // yourself blocked by this restriction, my best idea is:+ // (1) Add another `co_cleanup_member_task` (better name TBD), with+ // a new safety level `< unsafe_closure_internal`.+ // (2) Scan all mentions of `unsafe_closure_internal` to make sure+ // nothing assumes it is the lowest "safe" level. Importantly,+ // the `bind_captures_to_closure` logic related to+ // shared-cleanup in `async_now_closure` would continue to+ // test for `unsafe_closure_internal`, NOT the new level.+ // (3) Special-case `co_cleanup_member_task` in this check to+ // let its implicit object param be `<= shared_cleanup`.+ std::max(+ ArgSafety,+ safe_alias{+ to_underlying(safe_alias::closure_min_arg_safety) + 1})) || is_stateless_class_or_func<std::remove_reference_t<First>>))- ? SafeTaskRetAndArgs<ArgSafety, RetT, Args...>- : SafeTaskRetAndArgs<ArgSafety, RetT, First, Args...>;+ ? safe_task_ret_and_args<ArgSafety, RetT, Args...>+ : safe_task_ret_and_args<ArgSafety, RetT, First, Args...>; template <safe_alias ArgSafety, typename T, typename... Args>-class SafeTaskPromise final+class safe_task_promise final : public TaskPromiseWrapper< T,- SafeTask<ArgSafety, T>,+ safe_task<ArgSafety, T>, detail::TaskPromise<T>> { // "Unsafe" is not a "safe" task any more. In the future, we could have- // `SafeTask<unsafe, T>` act as `NowTask<T>`, but there's no present use+ // `safe_task<unsafe, T>` act as `now_task<T>`, but there's no present use // for this uniformity, but there are benefits to explicitness. static_assert( ArgSafety > safe_alias::unsafe,- "Instead of making an unsafe `SafeTask`, use a `NowTask`, or "+ "Instead of making an unsafe `safe_task`, use a `now_task`, or " "`async_now_closure()`"); public: // IMPORTANT: If you alter this arrangement, do the "Manual test" inside // `returnsVoid` in `SafeTaskTest.cpp`. //- // This is a no-op wrapper. It needs to exist because `IsSafeTaskValid`- // requires the coroutine function to be a complete type before checking- // if it's a stateless callable, and the easiest place to do that is in a- // class function that's guaranteed to be instantiated, such as this.- SafeTask<ArgSafety, T> get_return_object() noexcept {- // If your build failed here, your `SafeTask<>` coro declaration is+ // This is a no-op wrapper. It needs to exist because `is_safe_task_valid`+ // requires the coroutine function to be a complete type before checking if+ // it's a stateless callable in `is_safe_task_valid`. This is a good place+ // to do this, since this function is guaranteed to be instantiated.+ safe_task<ArgSafety, T> get_return_object() noexcept {+ // If your build failed here, your `safe_task<>` coro declaration is // invalid. Specific causes for this failure: // - One of the arguments, or the return value, contains "unsafe // aliasing" -- see `SafeAlias.h` for the details. Typical // causes include raw pointers, references, reference wrappers, etc. // - A stateful callable: lambda with captures, class with members, etc. static_assert(- detail::IsSafeTaskValid<ArgSafety, T, Args...>,- "Bad SafeTask: check for unsafe aliasing in arguments or return "- "type; also ensure your callable is stateless.");+ detail::is_safe_task_valid<ArgSafety, T, Args...>,+ "Bad safe_task: check for unsafe aliasing in arguments or return "+ "type; also ensure your callable is stateless (or, in the case of "+ "`member_task` -- explicitly specifies `safe_alias_of`)."); return TaskPromiseWrapper< T,- SafeTask<ArgSafety, T>,+ safe_task<ArgSafety, T>, detail::TaskPromise<T>>::get_return_object(); } };@@ -263,55 +314,55 @@ auto bind_captures_to_closure(auto&&, auto); template <safe_alias ArgSafety, typename T>-struct SafeTaskWithExecutorCfg : DoesNotWrapAwaitable {+struct safe_task_with_executor_cfg : DoesNotWrapAwaitable { using InnerTaskWithExecutorT = TaskWithExecutor<T>;- using WrapperTaskT = SafeTask<ArgSafety, T>;+ using WrapperTaskT = safe_task<ArgSafety, T>; }; template <safe_alias, typename>-struct SafeTaskWithExecutorBaseTraits;+struct safe_task_with_executor_base_traits; template <safe_alias ArgSafety, typename T> requires(ArgSafety >= safe_alias::closure_min_arg_safety)-struct SafeTaskWithExecutorBaseTraits<ArgSafety, T> {+struct safe_task_with_executor_base_traits<ArgSafety, T> { using type = TaskWithExecutorWrapperCrtp<- SafeTaskWithExecutor<ArgSafety, T>,- SafeTaskWithExecutorCfg<ArgSafety, T>>;+ safe_task_with_executor<ArgSafety, T>,+ safe_task_with_executor_cfg<ArgSafety, T>>; }; -// `MemberTask` and `ClosureTask` are immovable.+// `member_task` and `closure_task` are immovable. template <safe_alias ArgSafety, typename T> requires(ArgSafety < safe_alias::closure_min_arg_safety)-struct SafeTaskWithExecutorBaseTraits<ArgSafety, T> {- using type = AddMustAwaitImmediately<TaskWithExecutorWrapperCrtp<- SafeTaskWithExecutor<ArgSafety, T>,- SafeTaskWithExecutorCfg<ArgSafety, T>>>;+struct safe_task_with_executor_base_traits<ArgSafety, T> {+ using type = ext::wrap_must_use_immediately_t<TaskWithExecutorWrapperCrtp<+ safe_task_with_executor<ArgSafety, T>,+ safe_task_with_executor_cfg<ArgSafety, T>>>; }; template <safe_alias ArgSafety, typename T>-struct SafeTaskCfg : DoesNotWrapAwaitable {+struct safe_task_cfg : DoesNotWrapAwaitable { using ValueT = T; using InnerTaskT = Task<T>;- using TaskWithExecutorT = SafeTaskWithExecutor<ArgSafety, T>;+ using TaskWithExecutorT = safe_task_with_executor<ArgSafety, T>; // There is no `promise_type` here because it's added by `coroutine_traits`- // below. This is the mechanism that enables `SafeTaskPromise` to inspect+ // below. This is the mechanism that enables `safe_task_promise` to inspect // the specific arguments of the coroutine (including the implicit object // parameter), and fail the compilation if anything looks unsafe. using PromiseT = void; }; template <safe_alias ArgSafety, typename T>-struct SafeTaskBaseTraits {+struct safe_task_base_traits { using type =- TaskWrapperCrtp<SafeTask<ArgSafety, T>, SafeTaskCfg<ArgSafety, T>>;+ TaskWrapperCrtp<safe_task<ArgSafety, T>, safe_task_cfg<ArgSafety, T>>; }; -// `MemberTask` and `ClosureTask` are immovable.+// `member_task` and `closure_task` are immovable. template <safe_alias ArgSafety, typename T> requires(ArgSafety < safe_alias::closure_min_arg_safety)-struct SafeTaskBaseTraits<ArgSafety, T> {- using type = AddMustAwaitImmediately<- TaskWrapperCrtp<SafeTask<ArgSafety, T>, SafeTaskCfg<ArgSafety, T>>>;+struct safe_task_base_traits<ArgSafety, T> {+ using type = ext::wrap_must_use_immediately_t<+ TaskWrapperCrtp<safe_task<ArgSafety, T>, safe_task_cfg<ArgSafety, T>>>; }; } // namespace detail@@ -320,70 +371,74 @@ class BackgroundTask; // IMPORTANT: This omits `start()` because backgrounded tasks can easily-// outlive the references they took, defeating the purpose of `SafeTask`.+// outlive the references they took, defeating the purpose of `safe_task`. // See `BackgroundTask` instead. template <safe_alias ArgSafety, typename T>-class FOLLY_NODISCARD SafeTaskWithExecutor final- : public detail::SafeTaskWithExecutorBaseTraits<ArgSafety, T>::type {+class [[nodiscard]] safe_task_with_executor final+ : public detail::safe_task_with_executor_base_traits<ArgSafety, T>::type { protected:- using detail::SafeTaskWithExecutorBaseTraits<ArgSafety, T>::type::type;+ using detail::safe_task_with_executor_base_traits<ArgSafety, T>::type::type; template <safe_alias, typename> friend class BackgroundTask; // for `unwrapTaskWithExecutor()`, remove later public:+ template <safe_alias> using folly_private_safe_alias_t = safe_alias_constant<ArgSafety>; [[deprecated(- "`asUnsafe()` is provided as an escape hatch for interoperating with "+ "`as_unsafe()` is provided as an escape hatch for interoperating with " "older futures-based code, or other places not yet compatible with " "true structured concurrency patterns. Beware, the full `Task` API " "abounds with footguns like `start()` and `semi()` -- including UB, "- "leaks, and lost errors.")]]- TaskWithExecutor<T> asUnsafe() && {+ "leaks, and lost errors. See `folly/coro/safe/docs/AsUnsafe.md` for "+ " safe migration patterns.")]]+ TaskWithExecutor<T> as_unsafe() && { return std::move(*this).unwrapTaskWithExecutor(); } }; template <safe_alias ArgSafety, typename T>-class FOLLY_CORO_TASK_ATTRS SafeTask final- : public detail::SafeTaskBaseTraits<ArgSafety, T>::type {+class FOLLY_CORO_TASK_ATTRS safe_task final+ : public detail::safe_task_base_traits<ArgSafety, T>::type { protected: friend struct folly::coro::detail::SafeTaskTest; // to test `withNewSafety` template <safe_alias, typename>- friend class SafeTask; // `withNewSafety` makes a different `SafeTask`+ friend class safe_task; // `withNewSafety` makes a different `safe_task` template <auto> // uses `withNewSafety` friend auto detail::bind_captures_to_closure(auto&&, auto); template <safe_alias Safety, typename U>- friend auto toNowTask(SafeTask<Safety, U>);+ friend auto to_now_task(safe_task<Safety, U>); // The `async_closure` implementation is allowed to override the- // argument-deduced `safe_alias_of_v` for a `SafeTask` because+ // argument-deduced `lenient_safe_alias_of_v` for a `safe_task` because // `capture_safety` marks some coro-stored `*capture*`s as `unsafe` even // though they're safe -- to discourage users from moving them. template <safe_alias NewSafety>- SafeTask<NewSafety, T> withNewSafety() && {- return SafeTask<NewSafety, T>{std::move(*this).unwrapTask()};+ safe_task<NewSafety, T> withNewSafety() && {+ return safe_task<NewSafety, T>{std::move(*this).unwrapTask()}; } public:- using detail::SafeTaskBaseTraits<ArgSafety, T>::type::type;+ using detail::safe_task_base_traits<ArgSafety, T>::type::type;+ template <safe_alias> using folly_private_safe_alias_t = safe_alias_constant<ArgSafety>; [[deprecated(- "`asUnsafe()` is provided as an escape hatch for interoperating with "+ "`as_unsafe()` is provided as an escape hatch for interoperating with " "older futures-based code, or other places not yet compatible with " "true structured concurrency patterns. Beware, the full `Task` API " "abounds with footguns like `start()` and `semi()` -- including UB, "- "leaks, and lost errors.")]]- Task<T> asUnsafe() && {+ "leaks, and lost errors. See `folly/coro/safe/docs/AsUnsafe.md` for "+ " safe migration patterns.")]]+ Task<T> as_unsafe() && { return std::move(*this).unwrapTask(); } }; template <safe_alias Safety, typename T>-auto toNowTask(SafeTask<Safety, T> t) {- return NowTask<T>{std::move(t).unwrapTask()};+auto to_now_task(safe_task<Safety, T> t) {+ return now_task<T>{std::move(t).unwrapTask()}; } namespace detail {@@ -399,18 +454,19 @@ template <typename T> struct safe_task_traits<TaskWithExecutor<T>> : safe_task_traits<Task<T>> {}; template <typename T>-struct safe_task_traits<NowTask<T>> : safe_task_traits<Task<T>> {};+struct safe_task_traits<now_task<T>> : safe_task_traits<Task<T>> {}; template <typename T>-struct safe_task_traits<NowTaskWithExecutor<T>> : safe_task_traits<Task<T>> {};+struct safe_task_traits<now_task_with_executor<T>> : safe_task_traits<Task<T>> {+}; template <safe_alias ArgSafety, typename T>-struct safe_task_traits<SafeTask<ArgSafety, T>> {+struct safe_task_traits<safe_task<ArgSafety, T>> { static constexpr safe_alias arg_safety = ArgSafety; using return_type = T; }; template <safe_alias ArgSafety, typename T>-struct safe_task_traits<SafeTaskWithExecutor<ArgSafety, T>>- : safe_task_traits<SafeTask<ArgSafety, T>> {};+struct safe_task_traits<safe_task_with_executor<ArgSafety, T>>+ : safe_task_traits<safe_task<ArgSafety, T>> {}; } // namespace detail @@ -418,11 +474,11 @@ template <folly::safe_alias ArgSafety, typename T, typename... Args> struct folly::coro::- coroutine_traits<folly::coro::SafeTask<ArgSafety, T>, Args...> {- // UGH: Pass `Args...` into `SafeTaskPromise` because at this point, the+ coroutine_traits<folly::coro::safe_task<ArgSafety, T>, Args...> {+ // UGH: Pass `Args...` into `safe_task_promise` because at this point, the // coroutine function is still an incomplete type, and can't be validated. using promise_type =- folly::coro::detail::SafeTaskPromise<ArgSafety, T, Args...>;+ folly::coro::detail::safe_task_promise<ArgSafety, T, Args...>; }; #endif
@@ -16,9 +16,9 @@ #pragma once -#include <folly/coro/Noexcept.h>+#include <folly/coro/ValueOrFatal.h> #include <folly/coro/safe/SafeTask.h>-#include <folly/coro/safe/detail/AsyncClosureBindings.h>+#include <folly/coro/safe/detail/BindAsyncClosure.h> #include <folly/detail/tuple.h> #if FOLLY_HAS_IMMOVABLE_COROUTINES@@ -48,12 +48,11 @@ auto async_closure_make_cleanup_tuple( async_closure_private_t priv, auto&& arg, const exception_wrapper* err) {- // `co_cleanup` is allowed to return `Task<void>` or a tuple of them. auto to_lite_tuple = []<typename T>(T task) { static_assert(- noexcept_awaitable_v<T> && std::is_void_v<semi_await_result_t<T>>,- "`co_cleanup()` must return a `noexcept`-awaitable `void` coro. "- "Change your return type to `AsNoexcept<Task<>>` and don't throw.");+ value_only_awaitable_v<T> && std::is_void_v<semi_await_result_t<T>>,+ "`co_cleanup()` must return a value-only awaitable `void` coro. "+ "Change your return type to `value_or_fatal<Task<>>` and don't throw."); return lite_tuple::tuple{std::move(task)}; }; if constexpr (has_async_object_private_hack_co_cleanup<decltype(arg)>) {@@ -109,7 +108,7 @@ // "unsafe" types that don't expose the inner type's `safe_alias`. std::move(inner_mover), std::move(storage_ptr),- // We don't require a `SafeTask` for `co_cleanup` because the coro+ // We don't require a `safe_task` for `co_cleanup` because the coro // cannot outlive the object (or `exception_ptr*`) it references. manual_safe_val(std::move(reversed_noexcept_cleanups))...); },@@ -135,9 +134,10 @@ bool SetCancelTok, typename ResultT, safe_alias OuterSafety,- // This coro is noexcept-awaitable iff `async_closure_outer_coro_result` is- // `noexcept`. But we don't want to restrict it for coros that are not- // marked `AsNoexcept` -- this boolean toggles its "is noexcept" asserts.+ // This coro is value-only awaitable iff `async_closure_outer_coro_result`+ // is `noexcept`. But we don't want to restrict it for coros that are not+ // marked `value_or_fatal` -- this boolean toggles its "is noexcept"+ // asserts. bool AssertNoexcept, typename OuterResT = drop_unit_t<decltype(async_closure_outer_coro_result<AssertNoexcept>(@@ -145,8 +145,8 @@ std::declval<lift_unit_t<ResultT>&&>()))>> std::conditional_t< OuterSafety >= safe_alias::closure_min_arg_safety,- SafeTask<OuterSafety, OuterResT>,- NowTask<OuterResT>>+ safe_task<OuterSafety, OuterResT>,+ now_task<OuterResT>> async_closure_outer_coro( async_closure_private_t priv, auto inner_mover,@@ -158,7 +158,7 @@ } // Pass our cancellation token to args that want it for cleanup. The user- // code can throw -- e.g. `CancellationToken::merge()` may allocate.+ // code can throw -- e.g. `cancellation_token_merge()` may allocate. if constexpr (SetCancelTok) { const auto& ctok = co_await co_current_cancellation_token; inner_err = try_and_catch([&]() {@@ -219,11 +219,11 @@ // // Closure safety checks follow the model of `SafeTask.h` -- and actually // reuse most of that implementation by requiring the inner coro to be a-// `SafeTask`.+// `safe_task`. // // Note that we don't check whether the callable passed into `async_closure` // is stateless, and we don't need to -- it is executed eagerly, and may be-// a coroutine wrapper. The coro callable underlying the inner `SafeTask`+// a coroutine wrapper. The coro callable underlying the inner `safe_task` // will have been verified to be stateless. // // Future: An `AsyncGenerator` closure flavor is possible, just think about@@ -249,7 +249,7 @@ static constexpr bool has_safe_args = (OuterSafety >= safe_alias::closure_min_arg_safety); - // The reason we need `SafeTask` here is that it have already detected any+ // The reason we need `safe_task` here is that it have already detected any // by-reference arguments (impossible to detect otherwise), stateful // coros, and unsafe return types. static constexpr bool is_inner_coro_safe =@@ -267,12 +267,12 @@ static_assert( has_safe_args, "Args passed into `async_closure()` must have `safe_alias_of` of at "- "least `shared_cleanup`. `NowTask` and `async_now_closure()` do not "+ "least `shared_cleanup`. `now_task` and `async_now_closure()` do not " "have this constraint. To force a movable closure, use `manual_safe_*`," " and comment with a proof of why your usage is memory-safe."); static_assert( is_inner_coro_safe,- "`async_closure` currently only supports `SafeTask` as the inner coro.");+ "`async_closure` currently only supports `safe_task` as the inner coro."); return NoexceptWrap::wrap_with([&]() { return std::move(outer_mover_)(); }); } };@@ -324,9 +324,9 @@ decltype(auto) async_closure_resolve_backref( capture_private_t priv, auto& tup, Arg&) { constexpr auto Tag = Arg::folly_bindings_identifier_tag;- // `AsyncClosureBindings.h` populates tags via `named_bind_info_tag_v`, which+ // `BindAsyncClosure.h` populates tags via `named_bind_info_tag_v`, which // uses `no_tag_t` to mean "no tag was set" -- so you can't look it up.- static_assert(!std::is_same_v<folly::bindings::ext::no_tag_t, decltype(Tag)>);+ static_assert(!std::is_same_v<folly::bind::ext::no_tag_t, decltype(Tag)>); // This will fail on missing, or ambiguous tags. using Entry = decltype(async_closure_backref_get<Tag>(FOLLY_DECLVAL(ArgMap))); static_assert(@@ -344,9 +344,9 @@ return capture<Source&>(priv, forward_bind_wrapper(target.get_lref())); } -// Replace `"x"_id` backreferences in the args of `capture_in_place` and-// `capture_in_place_with` with `capture<T&>` references to the corresponding-// capture storage.+// Replace `"x"_id` backreferences in the args of `bind::capture_in_place` and+// `bind::capture_in_place_with` with `capture<T&>` references to the+// corresponding capture storage. // // Backrefs may ONLY point to capture storage -- any args moved into the inner // coro are subject to unspecified destruction order, and so could not safely@@ -361,14 +361,14 @@ struct async_closure_backref_populator< ArgMap, ArgI,- bind_wrapper_t<folly::bindings::detail::in_place_args_maker<T, Args...>>> {+ bind_wrapper_t<folly::bind::detail::in_place_args_maker<T, Args...>>> { using BindWrap =- bind_wrapper_t<folly::bindings::detail::in_place_args_maker<T, Args...>>;+ bind_wrapper_t<folly::bind::detail::in_place_args_maker<T, Args...>>; auto operator()(capture_private_t priv, auto& tup, BindWrap&& bw) const { return lite_tuple::apply( [&](Args&&... args) { return unsafe_tuple_to_bind_wrapper(- folly::bindings::make_in_place_with([&]() {+ bind::in_place_with([&]() { return T{[&]() -> decltype(auto) { if constexpr (requires(Args a) { a.folly_bindings_identifier_tag;@@ -431,34 +431,41 @@ lite_tuple::tuple<Ts...> storage_tuple_; }; -template <size_t StorageI, typename Bs>+template <size_t StorageI, typename Bs, bool DerefResult = false> decltype(auto) async_closure_bind_inner_coro_arg( capture_private_t priv, Bs& bs, auto& storage_ptr) {- if constexpr (is_async_closure_outer_stored_arg<Bs>) {- // "own": arg was already moved into `storage_ptr`.- auto& storage_ref = get_from_storage_ptr<StorageI>(storage_ptr);- static_assert(- std::is_same_v<- typename Bs::storage_type,- std::remove_reference_t<decltype(storage_ref)>>);- // `SharedCleanupClosure=true` preserves the `after_cleanup_ref_` prefix of- // the storage type.- return storage_ref.template to_capture_ref</*shared*/ true>(priv);- } else if constexpr (- // "own": Move stored `as_capture()` into inner coro.- is_instantiation_of_v<async_closure_inner_stored_arg, Bs> ||- // `scheduleSelfClosure` / `scheduleScopeClosure` self-references.- is_instantiation_of_v<async_closure_scope_self_ref_hack, Bs>) {- return typename Bs::storage_type{priv, std::move(bs.bindWrapper_)};- } else if constexpr (is_any_capture<Bs>) {- // "pass": Move `capture<Ref>` into the inner coro.- static_assert(std::is_reference_v<typename Bs::capture_type>);- return std::move(bs);- } else { // "regular": Non-`capture` binding.- static_assert(is_instantiation_of_v<async_closure_regular_arg, Bs>);- // We don't inspect `storage_type` here -- `detail/AsyncClosureBindings.h`- // should have ensured that `bind_info_t` was in a default, no-op state.- return std::move(bs).bindWrapper_.what_to_bind();+ auto fn = [&]() -> decltype(auto) {+ if constexpr (is_async_closure_outer_stored_arg<Bs>) {+ // "own": arg was already moved into `storage_ptr`.+ auto& storage_ref = get_from_storage_ptr<StorageI>(storage_ptr);+ static_assert(+ std::is_same_v<+ typename Bs::storage_type,+ std::remove_reference_t<decltype(storage_ref)>>);+ // `SharedCleanupClosure=true` preserves the `after_cleanup_ref_` prefix+ // of the storage type.+ return storage_ref.template to_capture_ref</*shared*/ true>(priv);+ } else if constexpr (+ // "own": Move stored `bind::capture()` into inner coro.+ is_instantiation_of_v<async_closure_inner_stored_arg, Bs> ||+ // `scheduleSelfClosure` / `scheduleScopeClosure` self-references.+ is_instantiation_of_v<async_closure_scope_self_ref_hack, Bs>) {+ return typename Bs::storage_type{priv, std::move(bs.bindWrapper_)};+ } else if constexpr (is_any_capture<Bs>) {+ // "pass": Move `capture<Ref>` into the inner coro.+ static_assert(std::is_reference_v<typename Bs::capture_type>);+ return std::move(bs);+ } else { // "regular": Non-`capture` binding.+ static_assert(is_instantiation_of_v<async_closure_regular_arg, Bs>);+ // We don't inspect `storage_type` here -- `detail/BindAsyncClosure.h`+ // should have ensured that `bind_info_t` was in a default, no-op state.+ return std::move(bs).bindWrapper_.what_to_bind();+ }+ };+ if constexpr (DerefResult) {+ return *fn();+ } else {+ return fn(); } } @@ -467,33 +474,12 @@ T value; }; -// Eagerly construct -- but do not await -- an `async_closure`:-// - Resolve bindings.-// - Construct & store args for the user-supplied inner coro.-// - For ensuring cleanup in the face of `bad_alloc`, pre-allocate the-// outer task & `co_cleanup` tasks, if needed.-// - Create the inner coro, passing it `capture` references, or -- if-// there are no `co_cleanup` args and no outer coro -- quack-alike-// owning wrappers.-// - Marks the final user-facing task with the `safe_alias` that-// describes the memory-safety of the closure's arguments.-// - Returns the task inside a wrapper that statically checks the memory-// safety of the return & `make_inner_coro` types when-// `release_outer_coro()` is called.-//-// NB: Due to the "omit outer coro" optimization, `release_outer_coro()`-// will in some cases return a no-overhead wrapper around the coro returned-// by `make_inner_coro()`.-//-// Rationale: "Eager" is the only option matching user expectations, since-// regular coroutine args are bound eagerly too. Implementation-wise, all-// `lang/Bindings.h` logic has to be resolved within the current statement,-// since the auxiliary reference-bearing objects aren't valid beyond that.-template <auto Cfg>-auto bind_captures_to_closure(auto&& make_inner_coro, auto safeties_and_binds) {- auto& [arg_safeties, b_tup] = safeties_and_binds;-- using BTupIs = std::make_index_sequence<std::tuple_size_v<decltype(b_tup)>>;+template <bool OnlyGetInnerCoroType, auto Cfg, typename BTup>+auto async_closure_inner_coro_and_storage(auto&& make_inner_coro, BTup& b_tup) {+ // Ensure the compiler doesn't waste cycles on the "coro type detection"+ // code-path for safe `async_closure` -- only `async_now_closure` needs it.+ static_assert(Cfg.emit_now_task || !OnlyGetInnerCoroType);+ using BTupIs = std::make_index_sequence<std::tuple_size_v<BTup>>; // For stored arg @ `i`, `VtagStorageIs[i]` is a `*storage_ptr` index. using VtagStorageIs = decltype(lite_tuple::apply( [&]<typename... Bs>(Bs&...) {@@ -541,68 +527,161 @@ }(lite_tuple::get<ArgIs>(tup))...); }(b_tup, BTupIs{}, VtagStorageIs{})); - auto raw_inner_coro = lite_tuple::apply(- [&]<typename... Bs>(Bs&... bs) {- return [&]<size_t... ArgIs, size_t... StorageIs>(- std::index_sequence<ArgIs...>, vtag_t<StorageIs...>) {- return make_inner_coro(- // Unpack `Bs`, `ArgIs`, and `StorageIs` jointly- [&]() -> decltype(auto) {- if constexpr (Cfg.is_invoke_member && ArgIs == 0) {- // We have a `FOLLY_INVOKE_MEMBER`. It accesses the- // member function via `.`, but this arg is expected to be- // `co_cleanup_capture<>` or `AsyncObjectPtr<>`, so we- // "magically" dereference it here.- //- // On safety: Below, we assert that it it made a- // `MemberTask<T>`, which `inner_rewrapped` will- // implicitly unwrap & mark with a higher safety level.- // `MemberTask` provides only a minimal safety- // attestation, namely (besides arg 1, the implicit object- // param), none of its args are taken by-reference. This- // is fine, since for `OuterSafety`, we will have- // accounted for all the args' safety levels.- return *async_closure_bind_inner_coro_arg<StorageIs, Bs>(- capture_private_t{}, bs, storage_ptr);- } else {- return async_closure_bind_inner_coro_arg<StorageIs, Bs>(- capture_private_t{}, bs, storage_ptr);- }- }()...);- }(BTupIs{}, VtagStorageIs{}); // `StorageIs` indexes into `storage_ptr`- },- b_tup);+ using StoragePtr = decltype(storage_ptr);+ // The `apply` + nested lambdas jointly iterate over several packs:+ // - Binding tuple elements: `Bs... bs` from `b_tup`, indexed by `ArgIs...`+ // - `StorageIs...` point into `storage_ptr` for owned captures.+ //+ // (1) The return type depends on `OnlyGetInnerCoroType`.+ //+ // `true`: Only used inside a `decltype()`, which drives the logic from+ // `async_closure_safeties_and_bindings` labeled `task_forces_shared_cleanup`.+ // The return type is `tuple<type_identity<InnerCoro>, unused storage_ptr>`.+ //+ // `false`: This is the evaluated path that makes the actual inner coro.+ //+ // (2) If `DerefResult` is `true` below, this means the callable is a+ // `FOLLY_INVOKE_MEMBER`. It accesses the member function via `.`, but this+ // arg is expected to be `co_cleanup_capture<>` or `AsyncObjectPtr<>`, so we+ // "magically" dereference it here.+ //+ // On member-invocation safety: `bind_captures_to_closure` will assert that+ // we made a `member_task<T>`, which `inner_rewrapped` will implicitly unwrap+ // & mark with a higher safety level. By itself, `member_task` provides only+ // a minimal safety attestation, namely:+ // - For the implicit object param `Arg1`,+ // strict_safe_alias_of_v<Arg1> > shared_cleanup+ // - None of the other args are taken by-reference.+ // This is fine, since for `OuterSafety`, we will have accounted for all the+ // args' safety levels.+ return lite_tuple::tuple{+ lite_tuple::apply(+ [&]<typename... Bs>(Bs&... bs) {+ return [&]<size_t... ArgIs, size_t... StorageIs>(+ std::index_sequence<ArgIs...>, vtag_t<StorageIs...>) {+ if constexpr (OnlyGetInnerCoroType) { // Non-evaluated, see (1)+ return type_identity<detected_or_t<+ void*,+ invoke_result_t,+ decltype(make_inner_coro),+ decltype(async_closure_bind_inner_coro_arg<+ StorageIs,+ Bs,+ /*DerefResult*/ Cfg.is_invoke_member && ArgIs == 0>(+ capture_private_t{},+ FOLLY_DECLVAL(Bs&),+ FOLLY_DECLVAL(StoragePtr&)))...>>{};+ } else { // Actually create a coro+ return make_inner_coro(+ async_closure_bind_inner_coro_arg<+ StorageIs,+ Bs,+ /*DerefResult*/ (Cfg.is_invoke_member && ArgIs == 0)>(+ capture_private_t{}, bs, storage_ptr)...);+ }+ }(BTupIs{}, VtagStorageIs{});+ },+ b_tup),+ std::move(storage_ptr)};+} - // First, unwrap `AsNoexcept` so that `safe_task_traits` below can work.- // We only allow `AsNoexcept` as the outer wrapper.- using NoexceptWrap = as_noexcept_rewrapper<decltype(raw_inner_coro)>;+// Eagerly construct -- but do not await -- an `async_closure`:+// - Resolve bindings. For `emit_now_closure` this can involve a tricky+// dance with trying two different versions of bindings, as described+// inside `async_closure_safeties_and_bindings`.+// - Construct & store args for the user-supplied inner coro.+// - For ensuring cleanup in the face of `bad_alloc`, pre-allocate the+// outer task & `co_cleanup` tasks, if needed.+// - Create the inner coro, passing it `capture` references, or -- if+// there are no `co_cleanup` args and no outer coro -- quack-alike+// owning wrappers.+// - Marks the final user-facing task with the `safe_alias` that+// describes the memory-safety of the closure's arguments.+// - Returns the task inside a wrapper that statically checks the memory+// safety of the return & `make_inner_coro` types when+// `release_outer_coro()` is called.+//+// NB: Due to the "omit outer coro" optimization, `release_outer_coro()`+// will in some cases return a no-overhead wrapper around the coro returned+// by `make_inner_coro()`.+//+// Rationale: "Eager" is the only option matching user expectations, since+// regular coroutine args are bound eagerly too. Implementation-wise, all+// `lang/bind/Bind.h` logic has to be resolved within the current statement,+// since the auxiliary reference-bearing objects aren't valid beyond that.+template <auto Cfg>+auto bind_captures_to_closure(auto&& make_inner_coro, auto safeties_and_binds) {+ // The comment in `async_closure_safeties_and_bindings` covers WHY there are+ // two `b_tup` flavors, and why the overall algorithm is correct. In brief:+ // - `b_tup` is the happy path.+ // - `b_tup_for_unsafe_task` is only used when it turns out that the inner+ // coro for an `async_now_closure` is an unsafe task. It is `nullptr` on+ // code paths where it definitely won't get used.+ auto& [arg_min_safety, b_tup, b_tup_for_unsafe_task] = safeties_and_binds;+ auto pick_safest_b_tup = [&]() -> auto& {+ // Avoid the compile-time cost of `unsafe_inner_coro_forces_shared_cleanup`+ if constexpr (+ !Cfg.emit_now_task ||+ decltype(arg_min_safety)::args_force_shared_cleanup) {+ return b_tup;+ } else {+ // Per the discussion in `async_closure_safeties_and_bindings`, if+ // `async_now_closure` produces a `safe_task`, then we don't have to+ // force shared-cleanup behavior, and may use the `b_tup` bindings.+ // Using `b_tup_for_unsafe_task` will cause the inner coro to see+ // capture-refs of lower safety (e.g. `after_cleanup_ref` instead of+ // `co_cleanup_safe_ref`).+ constexpr bool unsafe_inner_coro_forces_shared_cleanup =+ Cfg.emit_now_task &&+ (strict_safe_alias_of_v<typename std::tuple_element_t<+ 0,+ decltype(async_closure_inner_coro_and_storage<+ /*get type*/ true,+ Cfg>(+ static_cast<decltype(make_inner_coro)>(make_inner_coro),+ b_tup))>::type> < safe_alias::unsafe_closure_internal);+ if constexpr (unsafe_inner_coro_forces_shared_cleanup) {+ return b_tup_for_unsafe_task;+ } else { // Stateless inner coro+ return b_tup;+ }+ }+ };+ auto [raw_inner_coro, storage_ptr] =+ async_closure_inner_coro_and_storage</*get type*/ false, Cfg>(+ static_cast<decltype(make_inner_coro)>(make_inner_coro),+ pick_safest_b_tup());++ // First, unwrap `value_or_fatal` so that `safe_task_traits` below can work.+ // We only allow `value_or_fatal` as the outer wrapper.+ using NoexceptWrap = value_or_fatal_rewrapper<decltype(raw_inner_coro)>; auto unwrapped_inner = []<typename T>(T&& t) {- if constexpr (NoexceptWrap::as_noexcept_wrapped) {+ if constexpr (NoexceptWrap::value_or_fatal_wrapped) { return NoexceptWrap::unwrapTask(std::move(t)); } else {- return mustAwaitImmediatelyUnsafeMover(std::move(t))();+ return ::folly::ext::must_use_immediately_unsafe_mover(std::move(t))(); } }(std::move(raw_inner_coro)); // Compute the safety of the arguments being passed by the caller.- constexpr safe_alias OuterSafety = Cfg.force_shared_cleanup // making NowTask+ constexpr safe_alias OuterSafety = Cfg.emit_now_task ? safe_alias::unsafe- : vtag_least_safe_alias(decltype(arg_safeties){});+ : decltype(arg_min_safety)::parent_view; // Also check that the coroutine function's signature looks safe. constexpr safe_alias InnerSafety = safe_task_traits<decltype(unwrapped_inner)>::arg_safety; // This converts `raw_inner_task` into a "task mover" that can be plumbed // down to, and used by, `async_closure_outer_coro()`. We do 3 tricks here:- // - Wrap all tasks into a "mover" to handle immovables like `NowTask`.- // - For `ClosureTask`, we'll internally LIE about its safety to let it be+ // - Wrap all tasks into a "mover" to handle immovables like `now_task`.+ // - For `closure_task`, we'll internally LIE about its safety to let it be // `co_await`ed. Per below, that's OK thanks to `async_closure_wrap_coro`.- // - For `SafeTask` closures with the "no outer coro" optimization, we set+ // - For `safe_task` closures with the "no outer coro" optimization, we set // the inner coro's safety to `OuterSafety`, for reasons explained below. auto inner_mover = [&]() { // The first branch is always taken for safe/movable `async_closure()` // invocations. For `async_now_closure()`, this branch is taken iff the- // inner coro is a `ClosureTask` or other `SafeTask`.+ // inner coro is a `closure_task` or other `safe_task`. if constexpr (InnerSafety >= safe_alias::unsafe_closure_internal) { // In the presence of stored `capture`s, `InnerSafety` (as measured by // `safe_alias_of` on the inner coro) is not what we want. That's@@ -610,15 +689,16 @@ // to discourage them being moved out of the closure. Instead, we set // safety based on `vtag_safety_of_async_closure_args` (`OuterSafety`). //- // `ClosureTask` cannot be `co_await`ed, so clip to `>= min_arg_safety`.+ // `closure_task` cannot be `co_await`ed, so clip to `>= min_arg_safety`. // This is OK since `async_closure_wrap_coro` will later enforce: // OuterSafety >= closure_min_arg_safety constexpr auto newSafety = std::max(OuterSafety, safe_alias::closure_min_arg_safety);- return mustAwaitImmediatelyUnsafeMover(+ return ::folly::ext::must_use_immediately_unsafe_mover( std::move(unwrapped_inner).template withNewSafety<newSafety>()); } else { // The "new safety" rewrite doesn't apply to unsafe tasks!- return mustAwaitImmediatelyUnsafeMover(std::move(unwrapped_inner));+ return ::folly::ext::must_use_immediately_unsafe_mover(+ std::move(unwrapped_inner)); } }(); @@ -630,9 +710,9 @@ // would not be true e.g. if the user passed something like this: // [](int num, auto me) { return me->addNumber(num); } static_assert(- std::is_same_v<MemberTask<ResultT>, decltype(unwrapped_inner)> ==+ std::is_same_v<member_task<ResultT>, decltype(unwrapped_inner)> == Cfg.is_invoke_member,- "To use `MemberTask<>` coros with `async_closure`, you must pass "+ "To use `member_task<>` coros with `async_closure`, you must pass " "the callable as `FOLLY_INVOKE_MEMBER(memberName)`, and pass the " "instance's `capture`/`AsyncObjectPtr`/... as the first argument."); @@ -644,21 +724,21 @@ "Cannot `co_return *after_cleanup()` without a cleanup arg"); return std::move(inner_mover); } else {- return mustAwaitImmediatelyUnsafeMover(+ return ::folly::ext::must_use_immediately_unsafe_mover( async_closure_make_outer_coro< /*cancelTok*/ true, ResultT, OuterSafety,- NoexceptWrap::as_noexcept_wrapped>(+ NoexceptWrap::value_or_fatal_wrapped>( async_closure_private_t{}, std::move(inner_mover), std::move(storage_ptr))); } }(); - if constexpr (Cfg.force_shared_cleanup) {+ if constexpr (Cfg.emit_now_task) { return NoexceptWrap::wrap_with([&]() {- return toNowTask(std::move(outer_mover)());+ return to_now_task(std::move(outer_mover)()); }); } else { return async_closure_wrap_coro<
@@ -1,773 +0,0 @@-/*- * Copyright (c) Meta Platforms, Inc. and affiliates.- *- * Licensed under the Apache License, Version 2.0 (the "License");- * you may not use this file except in compliance with the License.- * You may obtain a copy of the License at- *- * http://www.apache.org/licenses/LICENSE-2.0- *- * Unless required by applicable law or agreed to in writing, software- * distributed under the License is distributed on an "AS IS" BASIS,- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.- * See the License for the specific language governing permissions and- * limitations under the License.- */--#pragma once--#include <compare>--#include <folly/coro/safe/Captures.h>-#include <folly/coro/safe/SafeAlias.h>--/// This header's `async_closure_safeties_and_bindings` implements the-/// argument-binding logic for `async_closure`.-///-/// Before reading further, make sure to get familiar with:-/// - `folly/lang/Bindings.md` for `bound_args` & friends.-/// - `docs/Captures.md` to understand the `capture` type wrappers, and the-/// safety upgrade/downgrade rules for passing them into closures.-/// In particular, know this distinction:-/// * "owned captures" look like `capture<V>`. These are wrappers tied-/// to the closure whose `as_capture()` created it. Note that a closure-/// with an outer coro will pass these as `capture<V&>` to the inner task.-/// * "capture references" are `capture<V&>` or `<V&&>, implicitly created-/// for the closure from any caller-provided `capture` (value or ref).-///-/// `async_closure` takes user-specified closure arguments as `bound_args`, an-/// immovable object with 1-expression lifetime. This header plumbs them-/// through some transformations. In opt builds, it is intended to be elided-/// by compiler's alias analysis (NB: this needs benchmarks & perhaps tweaks).-///-/// Every variant of `async_closure` invokes `..._safeties_and_bindings()`,-/// which does several jobs. Here's a summary of the data flow:-/// * Figure out if the closure needs an outer coro, or if it can be elided.-/// * Measure the safety of the closure, as seen by its caller. This uses-/// the safeties of the arguments **before** any transformations.-/// * Transform the arg tuple:-/// - Convert each entry of the `bound_args` into a `capture` ref (when-/// the caller gave us a `capture`), or one of 4 tag types-/// (`async_closure*_arg` or `async_closure*_self_ref_hack`). The tag-/// types tells the `async_closure` implementation whether to store the-/// arg, and how to bind it to the inner closure.-/// - Figure out the storage type for each `as_capture` binding using-/// `folly::bindings::binding_policy`, to support `make_in_place*`.-/// - Transform non-owned `capture`s via `to_capture_ref`. Parents'-/// owned captures are implicitly passed by-ref; `after_cleanup_` refs-/// are "upgraded" if possible. Docs in `Captures.md`.-/// - Apply special handling to the first argument when the closure runs-/// `FOLLY_INVOKE_MEMBER`.-/// - Other args are perfectly forwarded.-/// * Validate the user inputs and try to issue readable error messages.-/// Also check internal invariants.-///-/// After the validation & transformation, `detail/AsyncClosure.h` is-/// responsible for actually storing the args, and creating the coroutine.-///-/// ## Implementation glossary-///-/// This header classifies the bound arguments into a few categories:-/// * "owned capture" or just "own": Make a new `capture` whose storage (and-/// cleanup) belongs to this closure.-/// - These correspond to `async_closure_{inner,outer}_stored_arg`.-/// - If the closure is "shared cleanup", the safety of the new capture-/// is downgraded to `after_cleanup_ref`.-/// - The inner task sees a `capture` ref for closures with an outer-/// coro, and a `capture` value otherwise.-/// * "pass capture ref" or just "pass": A `capture` from the caller.-/// - These are passed as `capture<Ref>`, even if the input is a value.-/// - The inner coro may see upgraded safety relative to the caller.-/// * "regular arg": The easy / normal case -- simply bind a forwarding-/// reference from the caller to the inner coro. The reference is dressed-/// in `async_closure_regular_arg`.-/// * "self-reference hack": See `async_closure_scope_self_ref_hack`.--#if FOLLY_HAS_IMMOVABLE_COROUTINES-FOLLY_PUSH_WARNING-FOLLY_DETAIL_LITE_TUPLE_ADJUST_WARNINGS--namespace folly::coro {-class AsyncObject;-class AsyncScopeSlotObject;-template <typename, size_t>-class SafeAsyncScopeContextProxy;-template <typename>-class AsyncObjectNonSlotPtr;-} // namespace folly::coro--namespace folly::coro::detail {--template <safe_alias... Vs>-constexpr safe_alias vtag_least_safe_alias(vtag_t<Vs...>) {- return std::min({safe_alias::maybe_value, Vs...});-}--//-// There are 4 tag types here, which all quack the same interface:-// * `storage_type`: For storing "owned captures", but also for measuring-// "caller's point-of-view" safety of regular args.-// * `bindWrapper_`: A `bind_wrapper_t<T>`, which is literally just `T` but-// preserving the value category even for references. It has either the-// forwarding reference or the value being bound. Note that `T` is not-// necessarily related to `storage_type` -- for in-place construction, it-// is a "maker", which is implicitly-convertible to the `storage_type`.-//-// There's also 5th case without a tag type -- when passing capture refs,-// `async_closure_safeties_and_bindings()` simply emits an unwrapped `capture`,-// and `AsyncClosure.h` detects it via `is_any_capture<Bs>`.-//-// The goal is that none of the 5 cases instantiate any storage, or call-// copy/move constructors until the final moment, when we either:-// - pass the arg to the inner coro, or-// - store it in the outer coro's `unique_ptr<tuple<>>` of owned captures.-//--// This is just a fancy forwarding reference, never a value.-template <typename Storage, typename BindWrapper>-struct async_closure_regular_arg {- using storage_type = Storage;- BindWrapper bindWrapper_;-};--// Use `is_base_of` since `instantiation_of` cannot handle no-type templates-class async_closure_outer_stored_arg_base {};-template <typename T>-concept is_async_closure_outer_stored_arg =- std::is_base_of_v<async_closure_outer_stored_arg_base, T>;--// For a given closure, all `_stored_arg` tags are going to be of one flavor.-// With an outer coro, we capture info required to resolve backrefs. Also, the-// "has outer coro" decision isn't exported in any way besides the "inner" vs-// "outer" stored arg type. If useful, we could easily refactor this to be one-// `_owned_capture` tag type, and branch on `has_outer_coro` in-// `AsyncClosure.h`. Reworking things this way would make it possible to have-// an outer coro without storage, which might be needed if someone has a-// legitimate use-case for captures that define `setParentCancelToken()` or-// `setParentExecutor()` without defining `co_cleanup()` (dubious!).-template <is_any_capture Storage, typename BindWrapper, size_t ArgI, auto Tag>-struct async_closure_outer_stored_arg : async_closure_outer_stored_arg_base {- using storage_type = Storage;- constexpr static inline size_t arg_idx = ArgI;- constexpr static inline auto tag = Tag;- BindWrapper bindWrapper_;-};-template <is_any_capture Storage, typename BindWrapper>-struct async_closure_inner_stored_arg {- using storage_type = Storage;- BindWrapper bindWrapper_;-};--// ## Why does this `self_ref_hack` type even exist?-//-// To avoid synchronization costs, `folly::coro` async scopes disallow `add()`-// after `joinAsync()` has completed (violating this is UB). However, it is-// always safe to call `add()` on an async scope from a task running on that-// **same** scope, even if `joinAsync()` has already started. This is because-// any active scope awaitable prevents its `joinAsync()` from completing.-//-// Unfortunately, though it would be safe, you cannot directly pass an-// `async_arg_cleanup<Scope&>` ref (`shared_cleanup` safety) into a closure-// being scheduled on the SAME async scope (needs `>= co_cleanup_safe_ref`).-// That's because at compile-time, we cannot tell if the "scope being scheduled-// on" is the same as "the scope being referenced".-//-// Moreover, "pass a ref to the current scope" is the ONLY case that is always-// safe [1]. Otherwise, the closure with the scope ref could run `add()` when-// the (other) scope that it references had already been cleaned up.-//-// [1] Future: Thanks to `co_cleanup` ordering, it may later be feasible to-// allow scopes that are passed later (cleaned up earlier) to reference those-// that are passed earlier (cleaned up later).-//-// ## How does `self_ref_hack` help allow safe self-references for scopes?-//-// The solution is to introduce `scheduleScopeClosure()`, which acts just like-// `schedule(async_closure())`, but prepends an "implicit scope parameter" to-// the user-supplied `bound_args{}`.-//-// This implicit param is `self_ref_hack`, which is handled specially inside-// `detail/AsyncClosure*`. As a result, the user's inner task gets a-// `co_cleanup_capture<Scope&>` as its first argument.-//-// Analogously, `AsyncObject::scheduleSelfClosure()` uses this mechanism to-// allow sub-closures to safely reference the `Slot` of the object, which-// contains the "current" async scope.-//-// ## Why are all aspects of `self_ref_hack` protected?-//-// This is NOT safe to use in other settings, because:-// (1) We instantiates a new `capture` ref from a bare reference, bypassing the-// usual lifetime safety checks.-// (2) This `ref_hack` object is deliberately excluded from the final-// `async_closure`'s safety accounting -- it only affects the-// `shard_cleanup` downgrade.-// Both of these are ONLY okay because the sub-closure's own scope outlives it.-template <typename Storage, typename BindWrapper>-struct async_closure_scope_self_ref_hack {- using storage_type = Storage;-- protected:- template <size_t, typename Bs>- friend decltype(auto) async_closure_bind_inner_coro_arg(- capture_private_t, Bs&, auto&);- template <typename, size_t>- friend class folly::coro::SafeAsyncScopeContextProxy;- friend class folly::coro::AsyncScopeSlotObject;- // The innards are `protected`, since `transform_binding` lets this be- // supplied from outside the closure machinery. Any new client being- // added here must think THOROUGHLY about the risks in the class docblock!- explicit async_closure_scope_self_ref_hack(BindWrapper b)- : bindWrapper_(std::move(b)) {}- BindWrapper bindWrapper_;-};--struct binding_helper_cfg {- bool is_shared_cleanup_closure;- bool has_outer_coro;- bool in_safety_measurement_pass;- constexpr auto operator<=>(const binding_helper_cfg&) const = default;-};--template <typename Binding, auto Cfg, size_t ArgI>-class capture_binding_helper;--struct capture_ref_measurement_stub {};--// This helper class only has static members. It exists only so that the-// various functions can share some type aliases.-template <- std::derived_from<folly::bindings::ext::bind_info_t> auto BI,- typename BindingType,- auto Cfg,- size_t ArgI>-class capture_binding_helper<- folly::bindings::ext::binding_t<BI, BindingType>,- Cfg,- ArgI> {- private:- // A constraint on the template would make forward-declarations messy.- static_assert(std::is_same_v<decltype(Cfg), binding_helper_cfg>);-- using category_t = folly::bindings::ext::category_t;- using ST = typename folly::bindings::ext::binding_policy<- folly::bindings::ext::binding_t<BI, BindingType>>::storage_type;- using UncvrefST = std::remove_cvref_t<ST>;-- // "Pass capture ref" validation. Here, `ST` is either a value or a- // reference `capture`. `RetST` is the `capture<Ref>` for the inner task.- //- // There is no "business logic" here. This only documents the possible data- // flows, `static_assert`s a few common user errors for better compiler- // messages, and refers to the relevant unit tests.- template <typename RetST>- static constexpr void static_assert_passing_capture() {- using ArgT = typename UncvrefST::capture_type;- static_assert(std::is_reference_v<ST> == (BI.category == category_t::ref));- if constexpr (std::is_reference_v<ArgT>) { // Is `capture<Ref>`?- // Design note: Why do we automatically pass all `capture`s by-reference?- // As an alternative, recall that `folly::bindings` has `const_ref` /- // `mut_ref`. These modifiers could be hijacked as a mandatory marking- // for `captures` that get passed by-reference. That might seem more- // explicit, but also more confusing and harder to use:- // - `const_ref` / `mut_ref CANNOT be used for "regular" args -- they're- // by-reference iff the caller writes `T&` in the signature.- // - `capture`s are intended to belong to the parent closure, it rarely- // makes sense to copy or move them.- // - Syntactically, `capture`s behave like pointers.- // - If we needed an explicit `const_ref` / `mut_ref` only to pass- // `capture<Val>` as a `capture<Ref>`, then migrating a closure from- // "has outer coro" to "lacks outer coro" would require adding such a- // modifier at every callsite.- // - You can still move out the contents of a capture into a child by- // passing an rvalue ref as `std::move(cap)` to the child, or by- // passing the actual value via `*std::move(cap)`. Similarly, `*cap`- // would copy the value.- // N.B. We DO use `as_capture{const_ref{}}` etc in order to convert- // plain references from a parent coro into `capture` refs in a child- // closure, see "capture-by-reference" in `Captures.md`.- static_assert(- !std::is_reference_v<ST>,- "Pass `capture<Ref>` by value, do not use `const_ref` / `mut_ref`");- // Passing the caller's `capture<Ref>` makes a new `capture` ref object.- if constexpr (std::is_lvalue_reference_v<BindingType>) {- // Improve errors over just "deleted copy ctor".- static_assert(- !std::is_rvalue_reference_v<ArgT>,- "capture<V&&> is move-only. Try std::move(yourRef).");-- // `check_capture_lref_to_lref` tests this branch -- passing a- // `capture<Ref>` that the caller bound as an lvalue. We'll copy it,- // potentially upgrading `after_cleanup_capture` -> `capture.- } else {- // Cleanup args don't support rval refs. No user-facing message, since- // `co_cleanup_capture` would first have a constraint failure.- static_assert(!is_any_co_cleanup_capture<UncvrefST>);-- // Tests: `check_capture_lref_to_rref` & `check_capture_rref_to_rref`.- // An input `capture<Ref>` bound as an rvalue gives the child a- // `capture<Val&&>`.- //- // It is in some sense optional to support this, since users can- // pass around lval refs and `std::move(*argRef)` at the last- // minute. However, I wanted to encourage the best practice of- // `std::move(argRef)` at the outermost callsite that knows about- // the move. Reasons:- // - The initial `std::move(arg)` enables use-after-move linting- // in the outermost scope.- // - `capture<T&&>` is move-only, meaning subsequent scopes also- // get use-after-move linting.- // - Future: we could build a debug-only use-after-move runtime- // checker by adding some state on `capture`s.- }- } else { // Is `capture<Val>`?- // Cleanup args require an outer coro, so it should never be the case- // that `*co_cleanup_capture<Val>` is being passed to a child..- static_assert(!is_any_co_cleanup_capture<UncvrefST>);-- // Tested in `check_capture_val_to_ref`: `capture<Val>` is implicitly- // passed as `capture<Ref>`.- }- }-- template <typename T>- static constexpr auto store_as(auto bind_wrapper) {- if constexpr (Cfg.has_outer_coro) {- return async_closure_outer_stored_arg<- T,- decltype(bind_wrapper),- ArgI,- folly::bindings::ext::named_bind_info_tag_v<decltype(BI)>>{- .bindWrapper_ = std::move(bind_wrapper)};- } else {- return async_closure_inner_stored_arg<T, decltype(bind_wrapper)>{- .bindWrapper_ = std::move(bind_wrapper)};- }- }-- // "owned capture": The closure creates storage for `as_capture()` bindings- static constexpr auto store_capture_binding(auto bind_wrapper) {- static_assert(- !is_any_capture<ST>,- "Given a capture `c`, do not write `as_capture(c)` to pass it to a "- "closure. Just write `c` as the argument, and it'll automatically "- "be passed as a capture reference.");- if constexpr (has_async_closure_co_cleanup<ST>) {- static_assert(Cfg.has_outer_coro);- // Future: Add a toggle to emit `restricted_co_cleanup_capture`- return store_as<co_cleanup_capture<ST>>(std::move(bind_wrapper));- } else if constexpr (BI.captureKind_ == capture_kind::indirect) {- if constexpr (Cfg.is_shared_cleanup_closure) {- return store_as<after_cleanup_capture_indirect<ST>>(- std::move(bind_wrapper));- } else {- return store_as<capture_indirect<ST>>(std::move(bind_wrapper));- }- } else if constexpr (- !Cfg.has_outer_coro &&- // `make_in_place*` is often used for immovable types, so without an- // outer coro, they must be on-heap to pass ownership to the inner coro.- folly::bindings::ext::is_binding_t_type_in_place<BindingType> &&- // Heuristic: Moving a type is usually cheaper than putting it on- // the heap. If not, people can always use `capture_indirect` with- // `unique_ptr`... Or, we could later add new capture kinds, like- // `plain_auto_storage = 0`, `plain_heap`, and `plain_non_heap`.- !std::is_move_constructible_v<BindingType>) {- if constexpr (Cfg.is_shared_cleanup_closure) {- return store_as<after_cleanup_capture_heap<ST>>(- std::move(bind_wrapper));- } else {- return store_as<capture_heap<ST>>(std::move(bind_wrapper));- }- } else {- if constexpr (Cfg.is_shared_cleanup_closure) {- return store_as<after_cleanup_capture<ST>>(std::move(bind_wrapper));- } else {- return store_as<capture<ST>>(std::move(bind_wrapper));- }- }- }-- template <typename>- static inline constexpr bool is_supported_capture_bind_info_v = false;-- template <>- static inline constexpr bool- is_supported_capture_bind_info_v<capture_bind_info_t> = true;-- // Future: Right now, we only check that `"x"_id = ` tags are unique at time- // of use, and this only applies for stored captures. But, from a pure "code- // quality" point of view, it would be reasonable to demand that all tags are- // unique, and that they are all used. This could be done either as a linter- // or in this file, at some compile-time cost.- template <auto Tag>- static inline constexpr bool is_supported_capture_bind_info_v<- folly::bindings::ext::named_bind_info_t<Tag, capture_bind_info_t>> = true;-- public:- // Transforms the binding as per the file docblock, returns a new binding.- // (either one of the 4 tag types above, or `capture<Ref>`)- static constexpr auto transform_binding(auto bind_wrapper) {- if constexpr (is_supported_capture_bind_info_v<decltype(BI)>) {- // Implement "capture-by-reference", docs in `Captures.md`- if constexpr (BI.category == category_t::ref) {- // Test in `check_parent_capture_ref`- static_assert(std::is_reference_v<ST>);- static_assert(- !is_any_capture<UncvrefST>,- "Do not use `const_ref` / `mut_ref` verbs to pass a `capture` to "- "a child closure -- just pass it directly.");- // It should be hard to get a ref to a co_cleanup type- static_assert(!has_async_closure_co_cleanup<UncvrefST>);- if constexpr (Cfg.in_safety_measurement_pass) {- return capture_ref_measurement_stub{};- } else if constexpr (Cfg.is_shared_cleanup_closure) {- return after_cleanup_capture<ST>{- capture_private_t{}, std::move(bind_wrapper)};- } else {- return capture<ST>{capture_private_t{}, std::move(bind_wrapper)};- }- } else { // Tests in `check_stored_*`.- static_assert(!std::is_reference_v<ST>);- return store_capture_binding(std::move(bind_wrapper));- }- } else { // Bindings for arguments the closure does NOT store.- static_assert(- std::is_same_v<- vtag_t<BI>,- vtag_t<folly::bindings::ext::bind_info_t{}>>,- "`folly::bindings::` modifiers like `constant` (or `\"x\"_id = `) "- "only make sense with `as_capture()` bindings -- for example, to "- "move a mutable value into `const` capture storage. For regular "- "args, use `const` in the signature of your inner coro, and/or "- "`std::as_const` when passing the arg.");- // If we allowed `make_in_place` without `as_capture`, the argument would- // require a copy or a move to be passed to the inner task (which the- // type may not support). If `as_capture` isn't appropriate, the user- // can also work around that via `std::make_unique<TheirType>` and/or- // `as_capture_indirect`.- static_assert(- !folly::bindings::ext::is_binding_t_type_in_place<BindingType>,- "Did you mean `capture_in_place<T>(...)`?");- if constexpr (is_any_capture<UncvrefST>) { // Tests in `check_capture_*`- // Pass preexisting `capture`s (NOT owned by this closure).- // Future: Add a toggle to make `restricted_co_cleanup_capture` refs.- auto arg_ref =- std::move(bind_wrapper)- .what_to_bind()- .template to_capture_ref<Cfg.is_shared_cleanup_closure>(- capture_private_t{});- static_assert_passing_capture<decltype(arg_ref)>();- return std::move(arg_ref);- } else if constexpr (- is_instantiation_of_v<async_closure_scope_self_ref_hack, UncvrefST>) {- // This `ref_hack` type quacks like the `stored_arg` types, but we need- // to unwrap it for it to be handled correctly downstream.- return std::move(bind_wrapper).what_to_bind();- } else { // Test in `check_regular_args`- // "regular" args -- neither an owned capture (`as_capture()` et al),- // nor a parent's `capture`. Passed via forwarding reference.-- // This may be redundant, since `co_cleanup_capture` enforces that- // cleanup types are immovable. If we did allow passing bare- // `co_cleanup` types, it could violate memory safety protections for- // `async_closure`s. For `async_now_closure`, there is also no obvious- // use-case for passing `*captureVar` by-reference into the child.- static_assert(- !has_async_closure_co_cleanup<UncvrefST>,- "This argument implements `async_closure` cleanup, so you should "- "almost certainly pass it `as_capture()` -- or, if you already "- "have as a reference `capture`, by-value.");-- return async_closure_regular_arg<ST, decltype(bind_wrapper)>{- .bindWrapper_ = std::move(bind_wrapper)};- }- }- }-};--// See `vtag_safety_of_async_closure_args` for the docs.-// NB: As a nested lambda, this breaks on clang-17 due to compiler bugs.-template <bool ParentViewOfSafety, typename T>-auto vtag_safety_of_async_closure_arg() {- // "owned capture": `store_as` outputs `async_closure_*_stored_arg`.- if constexpr (- is_async_closure_outer_stored_arg<T> ||- is_instantiation_of_v<async_closure_inner_stored_arg, T>) {- using CT = typename T::storage_type::capture_type;- static_assert(!std::is_reference_v<CT>);- // Stored captures are as safe as the type being stored. For example, when- // a closure stores a `BackgroundTask<Safety, T>`, it cannot be safer than- // `Safety`. We don't use `safe_alias_of_v` here because `AsyncObject.h`- // specializes `capture_safety_impl_v`.- return vtag<capture_safety_impl_v<CT>>;- } else if constexpr ( //- is_instantiation_of_v<async_closure_scope_self_ref_hack, T>) {- // This is a closure made by `spawn_self_closure()` et al. It must:- // - Avoid marking the closure's outer task `shared_cleanup`, so it can- // still be added to the scope that made it (`if` branch).- // - Downgrade [*] the safety of its own captures (`else` branch).- //- // [*] It would be memory-unsafe to reference such captures from- // recursively scheduled closures on the same scope!- if constexpr (ParentViewOfSafety) {- return vtag<>;- } else {- constexpr auto storage_safety = safe_alias_of_v<typename T::storage_type>;- // In current usage, ref_hack can only contain `co_cleanup_capture<V&>`.- static_assert(storage_safety == safe_alias::shared_cleanup);- return vtag<storage_safety>;- }- } else if constexpr (is_any_capture<T>) {- // "pass capture ref": Output of the `to_capture_ref` branch.- static_assert(std::is_reference_v<typename T::capture_type>);- return vtag<safe_alias_of_v<T>>;- } else if constexpr (std::is_same_v<capture_ref_measurement_stub, T>) {- if constexpr (ParentViewOfSafety) {- // Only allow capture-by-reference in `async_now_closure`s- return vtag<safe_alias::unsafe>;- } else {- // But, don't do closure-internal downgrades, since `transform_bindings`- // tries to prevent it from taking in refs to co_cleanup types this way.- return vtag<>;- }- } else {- // "regular arg": A non-`capture` passed via forwarding reference.- static_assert(is_instantiation_of_v<async_closure_regular_arg, T>);- return vtag<safe_alias_of_v<typename T::storage_type>>;- }-}--// Returns a vtag of `safe_alias_v` for the storage type of the args that-// did not come from `store_capture_binding`.-//-// We have to special-case the stored ones because `Captures.h` marks the-// `capture` wrappers for on-closure stored values `unsafe` to discourage users-// from moving them from the original closure. And, the wrappers themselves-// check the safety of the underlying type (via `capture_safety`).-//-// The doc in `scheduleScopeClosure()` justifies why our first call to this-// function includes `ref_hack` args in the measurement (we want the-// closure's own args downgraded to `after_cleanup_ref` safety), but not in-// the second (we don't want the emitted `SafeTask` to be knocked down to-// `shared_cleanup` safety, since that would make it unschedulable).-template <bool ParentViewOfSafety, typename TransformedBindingList>-constexpr auto vtag_safety_of_async_closure_args() {- return []<typename... T>(tag_t<T...>) {- return value_list_concat_t<- vtag_t,- decltype(vtag_safety_of_async_closure_arg<- ParentViewOfSafety,- T>())...>{};- }(TransformedBindingList{});-}--template <typename BindingT>-constexpr bool capture_needs_outer_coro() {- using BP = folly::bindings::ext::binding_policy<BindingT>;- using ST = typename BP::storage_type;- return has_async_closure_co_cleanup<ST>;-}--struct async_closure_bindings_cfg {- bool force_outer_coro;- bool force_shared_cleanup;- bool is_invoke_member;-};--// For `is_invoke_member` closures, we must run an additional lifetime-safety-// check. For convenience, we also implicitly wrap the first argument with-// `as_capture` when that's the obviously right choice.-template <async_closure_bindings_cfg Cfg>-struct async_closure_invoke_member_bindings {- constexpr auto operator()(tag_t<>) { return tag<>; }- template <auto BI0, typename BT0, auto... BI, typename... BT>- constexpr auto operator()(- tag_t<- folly::bindings::ext::binding_t<BI0, BT0>,- folly::bindings::ext::binding_t<BI, BT>...>) {- using T = std::remove_cvref_t<BT0>;- constexpr bool arg0_is_non_owning_ptr =- // `transform_binding()` passes captures as non-owning refs- is_any_capture<T> ||- // Raw pointers are allowed in `async_now_closure()`- std::is_pointer_v<T> ||- is_instantiation_of_v<folly::coro::AsyncObjectNonSlotPtr, T> ||- // `scheduleScopeClosure` & `scheduleSelfClosure` give non-owning- // pointers. NB: This covers `SlotLimitedObjectPtr`.- is_instantiation_of_v<async_closure_scope_self_ref_hack, T>;- // Invoking a `MemberTask` requires `force_outer_coro` iff the first arg- // is an owning capture.- //- // NB: Both implicit & explicit `as_capture()`s are assumed to be owning,- // and thus also `force_outer_coro`.- //- // The reason that `force_outer_coro` is NOT done automatically is that- // it adds perf overhead, which would be easily avoided if the user made- // their member function `static` instead.- static_assert(- Cfg.force_outer_coro || !Cfg.is_invoke_member || arg0_is_non_owning_ptr,- "It looks like you want the `MemberTask` closure to own the object "- "instance. Use `async_now_closure(bound_args{&obj}, fn)` if that "- "applies. The next best approach is to make your task `static`, "- "with its first arg `auto self`. If that's not viable, then use "- "`async_closure_config{.force_outer_coro = true}` to allocate a "- "coro frame to own your object.");- // Syntax sugar: `as_capture()` may be left as implicit for the arg0- // "object parameter" of `FOLLY_INVOKE_MEMBER`.- if constexpr (- Cfg.is_invoke_member &&- // If arg0 is `as_capture()` or similar, don't double-wrap it.- !std::derived_from<decltype(BI0), capture_bind_info_t> &&- // Non-owning pointer-like things don't need to be captured.- !arg0_is_non_owning_ptr) {- static_assert(- // BT0 is a value for `make_in_place`, rval ref otherwise.- !std::is_lvalue_reference_v<BT0>,- "If you call `async_closure` with `FOLLY_INVOKE_MEMBER` and "- "a non-`capture` argument, then it has to be an r-value, so "- "that the closure can take ownership of the object instance. "- "Consider `folly::copy()` or `std::move()`.");- return tag<- folly::bindings::ext::- binding_t<as_capture_bind_info<capture_kind::plain>{}(BI0), BT0>,- folly::bindings::ext::binding_t<BI, BT>...>;- } else {- return tag<- folly::bindings::ext::binding_t<BI0, BT0>,- folly::bindings::ext::binding_t<BI, BT>...>;- }- }-};--// Converts forwarded arguments to bindings, figures out the storage policy-// (outer coro?, shared cleanup?), and applies `transform_bindings` to compute-// the final storage & binding outcome for each argument. The caller should-// create an outer coro iff the resulting `tuple` contains at least one-// `async_closure_outer_stored_arg`.-//-// Returns a pair:-// - vtag<safe_alias> computed as in `vtag_safety_of_async_closure_arg()`-// - transformed bindings: binding | async_closure_{inner,outer}_stored_arg-//-// NB: It's fine for this implementation detail to take `BoundArgs` by-ref-// because `async_closure` & friends took them by value.-template <async_closure_bindings_cfg Cfg, typename BoundArgs>-constexpr auto async_closure_safeties_and_bindings(BoundArgs&& bargs) {- using Bindings = decltype(async_closure_invoke_member_bindings<Cfg>{}(- typename BoundArgs::binding_list_t{}));-- auto tup = static_cast<BoundArgs&&>(bargs).unsafe_tuple_to_bind();- auto make_result_tuple =- [&]<binding_helper_cfg HelperCfg>(vtag_t<HelperCfg>) {- return [&]<size_t... Is>(std::index_sequence<Is...>) {- return lite_tuple::tuple{[&]() {- using Binding = type_list_element_t<Is, Bindings>;- using T = std::tuple_element_t<Is, decltype(tup)>;- return capture_binding_helper<Binding, HelperCfg, Is>::- transform_binding(bind_wrapper_t<T>{- .t_ = static_cast<T&&>(lite_tuple::get<Is>(tup))});- }()...};- }(std::make_index_sequence<type_list_size_v<Bindings>>{});- };-- // Future: If there are many `make_in_place` arguments (which require- // `capture_heap`), it may be more efficient to auto-select an outer coro,- // for just 2 heap allocations. Beware: this changes user-facing types- // (`capture_heap` to `capture`), but most users shouldn't depend on that.- constexpr bool has_outer_coro =- Cfg.force_outer_coro || []<typename... Bs>(tag_t<Bs...>) {- return (capture_needs_outer_coro<Bs>() || ...);- }(Bindings{});- // Figure out `IsSharedCleanupClosure` for `binding_cfg` for the real- // `transform_binding` call.- //- // Our choice of `is_shared_cleanup_closure = true` is important since we- // reuse this type list for the returned- // `vtag_safety_of_async_closure_args`. That vtag is used by- // `async_closure` to compute the safety level for the resulting- // `SafeTask`. This safety must NOT be increased by reference upgrades --- // a reference's safety is only upgraded inside the child closure, but the- // original safety applies in the parent closure, which is where the- // returned `vtag` is consumed.- //- // Choosing `true` here does not affect the `is_shared_cleanup` choice below- // It merely toggles between `after_cleanup_ref_capture` and `capture`, with- // either `after_cleanup_ref` or `co_cleanup_safe_ref` safety.- using shared_cleanup_transformed_binding_types = type_list_concat_t<- tag_t,- decltype(make_result_tuple(- vtag<binding_helper_cfg{- .is_shared_cleanup_closure = true,- .has_outer_coro = has_outer_coro,- .in_safety_measurement_pass = true}>))>;-- // Why do we evaluate arg safety with `ParentViewOfSafety == true` here,- // and with `false` in the returned `vtag_safety_of_async_closure_args`?- //- // This toggle supports two usage scenarios:- //- // (1) Capture-by-reference behaviors, like `capture_const_ref()` /- // `as_capture(const_ref())` et al.- // - `unsafe` for parent -- Since these are raw references from the- // parent's scope, ensure they're only allowed in `async_now_closure`s.- // - Ignored by child -- Simultaneously, we don't want the internal coro- // to be subject to "shared cleanup" downgrades. Doing that would,- // e.g., break the useful pattern of an on-closure scope collecting- // results on a parent collector passed via capture-by-reference.- //- // (2) Closures created by `spawn_self_closure()` et al. Also see- // `async_closure_scope_self_ref_hack`.- //- // - In the returned `vtag` that measures the parent's view of the safety- // of the closure, `ParentViewOfSafety == true` will exclude the- // closure's first arg (the scope or object ref) from the vtag -- it- // would otherwise be `shared_cleanup`. That is, of course, the entire- // point of `spawn_self_closure()` -- we happen to know that the scope- // ref is safe because of the circumstances of the closure's creation.- //- // - Using `ParentViewOfSafety = false` here makes `spawn_self_closure`s- // **internally** consider themselves to be `shared_cleanup` closures.- // I.e. `after_cleanup_` inputs are not upgraded, and owned captures are- // downgraded to `after_cleanup_`.- //- // To see that these downgrades are the correct behavior, imagine a chain- // of closures, each calling `spawn_self_closure()` to make the next.- // `SafeAsyncScope` awaits these concurrently, so they must not take- // dependencies on each other's owned captures.- constexpr auto internal_arg_min_safety = vtag_least_safe_alias(- vtag_safety_of_async_closure_args<- /*ParentViewOfSafety*/ false,- shared_cleanup_transformed_binding_types>());-- // Compute the `after_cleanup_` downgrade/upgrade behavior for the closure.- // Two possible scenarios:- // - An `async_closure` takes a `SafeTask` and emits a `SafeTask`. Then- // we'll have `==` iff we got a `co_cleanup_capture` ref from a parent.- // - An `async_closure` taking an unconstrained task (may have by-ref- // args, ref captures), and emitting a `NowTask`. In this case, the arg- // safety doesn't actually matter -- the caller must always- // `force_shared_cleanup` simply because the lambda callable might- // capture a `co_cleanup` ref inside it.- static_assert(- safe_alias::closure_min_arg_safety == safe_alias::shared_cleanup);- constexpr bool is_shared_cleanup = Cfg.force_shared_cleanup ||- (safe_alias::shared_cleanup >= internal_arg_min_safety);-- return lite_tuple::tuple{- // Safety of the closure's arguments from the parent's perspective- vtag_safety_of_async_closure_args<- /*ParentViewOfSafety*/ true,- shared_cleanup_transformed_binding_types>(),- // How the child closure should store and/or bind its arguments- make_result_tuple(- vtag<binding_helper_cfg{- .is_shared_cleanup_closure = is_shared_cleanup,- .has_outer_coro = has_outer_coro,- .in_safety_measurement_pass = false}>)};-}--} // namespace folly::coro::detail--FOLLY_POP_WARNING-#endif
@@ -0,0 +1,878 @@+/*+ * Copyright (c) Meta Platforms, Inc. and affiliates.+ *+ * Licensed under the Apache License, Version 2.0 (the "License");+ * you may not use this file except in compliance with the License.+ * You may obtain a copy of the License at+ *+ * http://www.apache.org/licenses/LICENSE-2.0+ *+ * Unless required by applicable law or agreed to in writing, software+ * distributed under the License is distributed on an "AS IS" BASIS,+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.+ * See the License for the specific language governing permissions and+ * limitations under the License.+ */++#pragma once++#include <compare>++#include <folly/coro/safe/BindCaptures.h>+#include <folly/coro/safe/Captures.h>+#include <folly/lang/SafeAlias.h>+#include <folly/lang/bind/Named.h> // See test `AsyncClosure.captureBackref`+#include <folly/lang/bind/NamedToStorage.h>++/// This header's `async_closure_safeties_and_bindings` implements the+/// argument-binding logic for `async_closure`.+///+/// Before reading further, make sure to get familiar with:+/// - `folly/lang/Bindings.md` for `bind::args` & friends.+/// - `docs/Captures.md` to understand the `capture` type wrappers, and the+/// safety upgrade/downgrade rules for passing them into closures.+/// In particular, know this distinction:+/// * "owned captures" look like `capture<V>`. These are wrappers tied+/// to the closure whose `bind::capture()` created it. Note that a closure+/// with an outer coro will pass these as `capture<V&>` to the inner task.+/// * "capture references" are `capture<V&>` or `<V&&>, implicitly created+/// for the closure from any caller-provided `capture` (value or ref).+///+/// `async_closure` takes user-specified closure arguments as `bind::args`, an+/// immovable object with 1-expression lifetime. This header plumbs them+/// through some transformations. In opt builds, it is intended to be elided+/// by compiler's alias analysis (NB: this needs benchmarks & perhaps tweaks).+///+/// Every variant of `async_closure` invokes `..._safeties_and_bindings()`,+/// which does several jobs. Here's a summary of the data flow:+/// * Figure out if the closure needs an outer coro, or if it can be elided.+/// * Measure the safety of the closure, as seen by its caller. This uses+/// the safeties of the arguments **before** any transformations.+/// * Transform the arg tuple:+/// - Convert each entry of the `bind::args` into a `capture` ref (when+/// the caller gave us a `capture`), or one of 4 tag types+/// (`async_closure*_arg` or `async_closure*_self_ref_hack`). The tag+/// types tells the `async_closure` implementation whether to store the+/// arg, and how to bind it to the inner closure.+/// - Figure out the storage type for each `bind::capture` binding using+/// `bind::bind_to_storage_policy`, to support `bind::in_place*`.+/// - Transform non-owned `capture`s via `to_capture_ref`. Parents'+/// owned captures are implicitly passed by-ref; `after_cleanup_` refs+/// are "upgraded" if possible. Docs in `Captures.md`.+/// - Apply special handling to the first argument when the closure runs+/// `FOLLY_INVOKE_MEMBER`.+/// - Other args are perfectly forwarded.+/// * Validate the user inputs and try to issue readable error messages.+/// Also check internal invariants.+///+/// After the validation & transformation, `detail/AsyncClosure.h` is+/// responsible for actually storing the args, and creating the coroutine.+///+/// ## Implementation glossary+///+/// This header classifies the bound arguments into a few categories:+/// * "owned capture" or just "own": Make a new `capture` whose storage (and+/// cleanup) belongs to this closure.+/// - These correspond to `async_closure_{inner,outer}_stored_arg`.+/// - If the closure is shared-cleanup, the safety of the new capture+/// is downgraded to `after_cleanup_ref`.+/// - The inner task sees a `capture` ref for closures with an outer+/// coro, and a `capture` value otherwise.+/// * "pass capture ref" or just "pass": A `capture` from the caller.+/// - These are passed as `capture<Ref>`, even if the input is a value.+/// - The inner coro may see upgraded safety relative to the caller.+/// * "regular arg": The easy / normal case -- simply bind a forwarding+/// reference from the caller to the inner coro. The reference is dressed+/// in `async_closure_regular_arg`.+/// * "self-reference hack": See `async_closure_scope_self_ref_hack`.++#if FOLLY_HAS_IMMOVABLE_COROUTINES+FOLLY_PUSH_WARNING+FOLLY_DETAIL_LITE_TUPLE_ADJUST_WARNINGS++// Future: If this is used anywhere else, move it to `BindCapturesToStorage.h`.+//+// We extended `bind::ext::bind_info_t` with `capture_kind`, so we must+// explicitly specialize `bind_to_storage_policy`. We reuse the standard+// rules. Custom `capture` binding logic lives in `BindAsyncClosure.h`.+namespace folly::bind::ext {+template <auto BI, typename BindingType>+ requires std::same_as< // Written as a constraint to prevent object slicing+ decltype(BI),+ ::folly::bind::detail::capture_bind_info_t>+class bind_to_storage_policy<binding_t<BI, BindingType>> {+ private:+ using standard =+ bind_to_storage_policy<binding_t<bind_info_t{BI}, BindingType>>;++ public:+ using storage_type = typename standard::storage_type;+ using signature_type = typename standard::signature_type;+};+} // namespace folly::bind::ext++namespace folly::coro {+class AsyncObject;+class AsyncScopeSlotObject;+template <typename, size_t>+class safe_async_scope_context_proxy;+template <typename>+class AsyncObjectNonSlotPtr;+} // namespace folly::coro++namespace folly::coro::detail {++template <safe_alias... Vs>+constexpr safe_alias vtag_least_safe_alias(vtag_t<Vs...>) {+ return std::min({safe_alias::maybe_value, Vs...});+}++//+// There are 4 tag types here, which all quack the same interface:+// * `storage_type`: For storing "owned captures", but also for measuring+// "caller's point-of-view" safety of regular args.+// * `bindWrapper_`: A `bind_wrapper_t<T>`, which is literally just `T` but+// preserving the value category even for references. It has either the+// forwarding reference or the value being bound. Note that `T` is not+// necessarily related to `storage_type` -- for in-place construction, it+// is a "maker", which is implicitly-convertible to the `storage_type`.+//+// There's also 5th case without a tag type -- when passing capture refs,+// `async_closure_safeties_and_bindings()` simply emits an unwrapped `capture`,+// and `AsyncClosure.h` detects it via `is_any_capture<Bs>`.+//+// The goal is that none of the 5 cases instantiate any storage, or call+// copy/move constructors until the final moment, when we either:+// - pass the arg to the inner coro, or+// - store it in the outer coro's `unique_ptr<tuple<>>` of owned captures.+//++// This is just a fancy forwarding reference, never a value.+template <typename Storage, typename BindWrapper>+struct async_closure_regular_arg {+ using storage_type = Storage;+ BindWrapper bindWrapper_;+};++// Use `is_base_of` since `instantiation_of` cannot handle no-type templates+class async_closure_outer_stored_arg_base {};+template <typename T>+concept is_async_closure_outer_stored_arg =+ std::is_base_of_v<async_closure_outer_stored_arg_base, T>;++// For a given closure, all `_stored_arg` tags are going to be of one flavor.+// With an outer coro, we capture info required to resolve backrefs. Also, the+// "has outer coro" decision isn't exported in any way besides the "inner" vs+// "outer" stored arg type. If useful, we could easily refactor this to be one+// `_owned_capture` tag type, and branch on `has_outer_coro` in+// `AsyncClosure.h`. Reworking things this way would make it possible to have+// an outer coro without storage, which might be needed if someone has a+// legitimate use-case for captures that define `setParentCancelToken()` or+// `setParentExecutor()` without defining `co_cleanup()` (dubious!).+template <is_any_capture Storage, typename BindWrapper, size_t ArgI, auto Tag>+struct async_closure_outer_stored_arg : async_closure_outer_stored_arg_base {+ using storage_type = Storage;+ constexpr static inline size_t arg_idx = ArgI;+ constexpr static inline auto tag = Tag;+ BindWrapper bindWrapper_;+};+template <is_any_capture Storage, typename BindWrapper>+struct async_closure_inner_stored_arg {+ using storage_type = Storage;+ BindWrapper bindWrapper_;+};++// ## Why does this `self_ref_hack` type even exist?+//+// To avoid synchronization costs, `folly::coro` async scopes disallow `add()`+// after `joinAsync()` has completed (violating this is UB). However, it is+// always safe to call `add()` on an async scope from a task running on that+// **same** scope, even if `joinAsync()` has already started. This is because+// any active scope awaitable prevents its `joinAsync()` from completing.+//+// Unfortunately, though it would be safe, you cannot directly pass an+// `async_arg_cleanup<Scope&>` ref (`shared_cleanup` safety) into a closure+// being scheduled on the SAME async scope (needs `>= co_cleanup_safe_ref`).+// That's because at compile-time, we cannot tell if the "scope being scheduled+// on" is the same as "the scope being referenced".+//+// Moreover, "pass a ref to the current scope" is the ONLY case that is always+// safe [1]. Otherwise, the closure with the scope ref could run `add()` when+// the (other) scope that it references had already been cleaned up.+//+// [1] Future: Thanks to `co_cleanup` ordering, it may later be feasible to+// allow scopes that are passed later (cleaned up earlier) to reference those+// that are passed earlier (cleaned up later).+//+// ## How does `self_ref_hack` help allow safe self-references for scopes?+//+// The solution is to introduce `scheduleScopeClosure()`, which acts just like+// `schedule(async_closure())`, but prepends an "implicit scope parameter" to+// the user-supplied `bind::args{}`.+//+// This implicit param is `self_ref_hack`, which is handled specially inside+// `detail/AsyncClosure*`. As a result, the user's inner task gets a+// `co_cleanup_capture<Scope&>` as its first argument.+//+// Analogously, `AsyncObject::scheduleSelfClosure()` uses this mechanism to+// allow sub-closures to safely reference the `Slot` of the object, which+// contains the "current" async scope.+//+// ## Why are all aspects of `self_ref_hack` protected?+//+// This is NOT safe to use in other settings, because:+// (1) We instantiates a new `capture` ref from a bare reference, bypassing the+// usual lifetime safety checks.+// (2) This `ref_hack` object is deliberately excluded from the final+// `async_closure`'s safety accounting -- it only affects the+// `shard_cleanup` downgrade.+// Both of these are ONLY okay because the sub-closure's own scope outlives it.+template <typename Storage, typename BindWrapper>+struct async_closure_scope_self_ref_hack {+ using storage_type = Storage;++ protected:+ template <size_t, typename Bs, bool>+ friend decltype(auto) async_closure_bind_inner_coro_arg(+ capture_private_t, Bs&, auto&);+ template <typename, size_t>+ friend class folly::coro::safe_async_scope_context_proxy;+ friend class folly::coro::AsyncScopeSlotObject;+ // The innards are `protected`, since `transform_binding` lets this be+ // supplied from outside the closure machinery. Any new client being+ // added here must think THOROUGHLY about the risks in the class docblock!+ explicit async_closure_scope_self_ref_hack(BindWrapper b)+ : bindWrapper_(std::move(b)) {}+ BindWrapper bindWrapper_;+};++struct binding_helper_cfg {+ bool is_shared_cleanup_closure;+ bool has_outer_coro;+ bool in_safety_measurement_pass;+ constexpr auto operator<=>(const binding_helper_cfg&) const = default;+};++template <typename Binding, auto Cfg, size_t ArgI>+class capture_binding_helper;++struct capture_ref_measurement_stub {};++// This helper class only has static members. It exists only so that the+// various functions can share some type aliases.+template <+ std::derived_from<bind::ext::bind_info_t> auto BI,+ typename BindingType,+ auto Cfg,+ size_t ArgI>+class capture_binding_helper<bind::ext::binding_t<BI, BindingType>, Cfg, ArgI> {+ private:+ // A constraint on the template would make forward-declarations messy.+ static_assert(std::is_same_v<decltype(Cfg), binding_helper_cfg>);++ using category_t = bind::ext::category_t;+ using ST = typename bind::ext::bind_to_storage_policy<+ bind::ext::binding_t<BI, BindingType>>::storage_type;+ using UncvrefST = std::remove_cvref_t<ST>;++ // "Pass capture ref" validation. Here, `ST` is either a value or a+ // reference `capture`. `RetST` is the `capture<Ref>` for the inner task.+ //+ // There is no "business logic" here. This only documents the possible data+ // flows, `static_assert`s a few common user errors for better compiler+ // messages, and refers to the relevant unit tests.+ template <typename RetST>+ static constexpr void static_assert_passing_capture() {+ using ArgT = typename UncvrefST::capture_type;+ static_assert(std::is_reference_v<ST> == (BI.category == category_t::ref));+ if constexpr (std::is_reference_v<ArgT>) { // Is `capture<Ref>`?+ // Design note: Why do we automatically pass all `capture`s by-reference?+ //+ // As an alternative, recall we have `bind::const_ref` / `bind::mut_ref`.+ // These are now used for `bind::capture_const_ref` and+ // `bind::capture_mut_ref` with different semantics (below). Why didn't+ // we instead use these as mandatory markings for `captures` that get+ // passed by-reference. That might seem more explicit, but also more+ // confusing and harder to use:+ // - `const_ref` / `mut_ref CANNOT be used for "regular" args -- they're+ // by-reference iff the caller writes `T&` in the signature.+ // - `capture`s are intended to belong to the parent closure, it rarely+ // makes sense to copy or move them.+ // - Syntactically, `capture`s behave like pointers.+ // - If we needed an explicit `const_ref` / `mut_ref` only to pass+ // `capture<Val>` as a `capture<Ref>`, then migrating a closure from+ // "has outer coro" to "lacks outer coro" would require adding such a+ // modifier at every callsite.+ // - You can still move out the contents of a capture into a child by+ // passing an rvalue ref as `std::move(cap)` to the child, or by+ // passing the actual value via `*std::move(cap)`. Similarly, `*cap`+ // would copy the value.+ //+ // N.B. We DO use `bind::capture{const_ref{}}` etc in order to convert+ // plain references from a parent coro into `capture` refs in a child+ // closure, see "capture-by-reference" in `Captures.md`.+ static_assert(+ !std::is_reference_v<ST>,+ "Pass `capture<Ref>` by value, do not use `const_ref` / `mut_ref`");+ // Passing the caller's `capture<Ref>` makes a new `capture` ref object.+ if constexpr (std::is_lvalue_reference_v<BindingType>) {+ // Improve errors over just "deleted copy ctor".+ static_assert(+ !std::is_rvalue_reference_v<ArgT>,+ "capture<V&&> is move-only. Try std::move(yourRef).");++ // `check_capture_lref_to_lref` tests this branch -- passing a+ // `capture<Ref>` that the caller bound as an lvalue. We'll copy it,+ // potentially upgrading `after_cleanup_capture` -> `capture.+ } else {+ // Cleanup args don't support rval refs. No user-facing message, since+ // `co_cleanup_capture` would first have a constraint failure.+ static_assert(!is_any_co_cleanup_capture<UncvrefST>);++ // Tests: `check_capture_lref_to_rref` & `check_capture_rref_to_rref`.+ // An input `capture<Ref>` bound as an rvalue gives the child a+ // `capture<Val&&>`.+ //+ // It is in some sense optional to support this, since users can+ // pass around lval refs and `std::move(*argRef)` at the last+ // minute. However, I wanted to encourage the best practice of+ // `std::move(argRef)` at the outermost callsite that knows about+ // the move. Reasons:+ // - The initial `std::move(arg)` enables use-after-move linting+ // in the outermost scope.+ // - `capture<T&&>` is move-only, meaning subsequent scopes also+ // get use-after-move linting.+ // - Future: we could build a debug-only use-after-move runtime+ // checker by adding some state on `capture`s.+ }+ } else { // Is `capture<Val>`?+ // Cleanup args require an outer coro, so it should never be the case+ // that `*co_cleanup_capture<Val>` is being passed to a child..+ static_assert(!is_any_co_cleanup_capture<UncvrefST>);++ // Tested in `check_capture_val_to_ref`: `capture<Val>` is implicitly+ // passed as `capture<Ref>`.+ }+ }++ template <typename T>+ static constexpr auto store_as(auto bind_wrapper) {+ if constexpr (Cfg.has_outer_coro) {+ return async_closure_outer_stored_arg<+ T,+ decltype(bind_wrapper),+ ArgI,+ bind::ext::named_bind_info_tag_v<decltype(BI)>>{+ .bindWrapper_ = std::move(bind_wrapper)};+ } else {+ return async_closure_inner_stored_arg<T, decltype(bind_wrapper)>{+ .bindWrapper_ = std::move(bind_wrapper)};+ }+ }++ // "owned capture": The closure creates storage for `bind::capture()` bindings+ static constexpr auto store_capture_binding(auto bind_wrapper) {+ static_assert(+ !is_any_capture<ST>,+ "Given a capture `c`, do not write `bind::capture(c)` to pass it to a "+ "closure. Just write `c` as the argument, and it'll automatically "+ "be passed as a capture reference.");+ if constexpr (has_async_closure_co_cleanup<ST>) {+ static_assert(Cfg.has_outer_coro);+ // Future: Add a toggle to emit `restricted_co_cleanup_capture`+ return store_as<co_cleanup_capture<ST>>(std::move(bind_wrapper));+ } else if constexpr (+ BI.captureKind_ == bind::detail::capture_kind::indirect) {+ if constexpr (Cfg.is_shared_cleanup_closure) {+ return store_as<after_cleanup_capture_indirect<ST>>(+ std::move(bind_wrapper));+ } else {+ return store_as<capture_indirect<ST>>(std::move(bind_wrapper));+ }+ } else if constexpr (+ !Cfg.has_outer_coro &&+ // `bind::in_place*` is often used for immovable types, so without an+ // outer coro, they must be on-heap to pass ownership to the inner coro.+ bind::ext::is_binding_t_type_in_place<BindingType> &&+ // Heuristic: Moving a type is usually cheaper than putting it on+ // the heap. If not, people can always use `capture_indirect` with+ // `unique_ptr`... Or, we could later add new capture kinds, like+ // `plain_auto_storage = 0`, `plain_heap`, and `plain_non_heap`.+ !std::is_move_constructible_v<BindingType>) {+ if constexpr (Cfg.is_shared_cleanup_closure) {+ return store_as<after_cleanup_capture_heap<ST>>(+ std::move(bind_wrapper));+ } else {+ return store_as<capture_heap<ST>>(std::move(bind_wrapper));+ }+ } else {+ if constexpr (Cfg.is_shared_cleanup_closure) {+ return store_as<after_cleanup_capture<ST>>(std::move(bind_wrapper));+ } else {+ return store_as<capture<ST>>(std::move(bind_wrapper));+ }+ }+ }++ template <typename>+ static inline constexpr bool is_supported_capture_bind_info_v = false;++ template <>+ inline constexpr bool+ is_supported_capture_bind_info_v<bind::detail::capture_bind_info_t> =+ true;++ // Future: Right now, we only check that `"x"_id = ` tags are unique at time+ // of use, and this only applies for stored captures. But, from a pure "code+ // quality" point of view, it would be reasonable to demand that all tags are+ // unique, and that they are all used. This could be done either as a linter+ // or in this file, at some compile-time cost.+ template <auto Tag>+ static inline constexpr bool is_supported_capture_bind_info_v<+ bind::ext::named_bind_info_t<Tag, bind::detail::capture_bind_info_t>> =+ true;++ public:+ // Transforms the binding as per the file docblock, returns a new binding.+ // (either one of the 4 tag types above, or `capture<Ref>`)+ static constexpr auto transform_binding(auto bind_wrapper) {+ if constexpr (is_supported_capture_bind_info_v<decltype(BI)>) {+ // Implement "capture-by-reference", docs in `Captures.md`+ if constexpr (BI.category == category_t::ref) {+ // Test in `check_parent_capture_ref`+ static_assert(std::is_reference_v<ST>);+ static_assert(+ !is_any_capture<UncvrefST>,+ "Do not use `const_ref` / `mut_ref` verbs to pass a `capture` to "+ "a child closure -- just pass it directly.");+ // It should be hard to get a ref to a co_cleanup type+ static_assert(!has_async_closure_co_cleanup<UncvrefST>);+ if constexpr (Cfg.in_safety_measurement_pass) {+ return capture_ref_measurement_stub{};+ } else if constexpr (Cfg.is_shared_cleanup_closure) {+ return after_cleanup_capture<ST>{+ capture_private_t{}, std::move(bind_wrapper)};+ } else {+ return capture<ST>{capture_private_t{}, std::move(bind_wrapper)};+ }+ } else { // Tests in `check_stored_*`.+ static_assert(!std::is_reference_v<ST>);+ return store_capture_binding(std::move(bind_wrapper));+ }+ } else { // Bindings for arguments the closure does NOT store.+ static_assert(+ std::is_same_v<vtag_t<BI>, vtag_t<bind::ext::bind_info_t{}>>,+ "`folly::bind::` modifiers like `constant` (or `\"x\"_id = `) "+ "only make sense with `bind::capture()` bindings -- for example, to "+ "move a mutable value into `const` capture storage. For regular "+ "args, use `const` in the signature of your inner coro, and/or "+ "`std::as_const` when passing the arg.");+ // If we allowed `bind::in_place` without `bind::capture`, the argument+ // would require a copy or a move to be passed to the inner task (which+ // the type may not support). If `bind::capture` isn't appropriate, the+ // user can also work around that via `std::make_unique<TheirType>` and/or+ // `bind::capture_indirect`.+ static_assert(+ !bind::ext::is_binding_t_type_in_place<BindingType>,+ "Did you mean `bind::capture_in_place<T>(...)`?");+ if constexpr (is_any_capture<UncvrefST>) { // Tests in `check_capture_*`+ // Pass preexisting `capture`s (NOT owned by this closure).+ // Future: Add a toggle to make `restricted_co_cleanup_capture` refs.+ auto arg_ref =+ std::move(bind_wrapper)+ .what_to_bind()+ .template to_capture_ref<Cfg.is_shared_cleanup_closure>(+ capture_private_t{});+ static_assert_passing_capture<decltype(arg_ref)>();+ return std::move(arg_ref);+ } else if constexpr (+ is_instantiation_of_v<async_closure_scope_self_ref_hack, UncvrefST>) {+ // This `ref_hack` type quacks like the `stored_arg` types, but we need+ // to unwrap it for it to be handled correctly downstream.+ return std::move(bind_wrapper).what_to_bind();+ } else { // Test in `check_regular_args`+ // "regular" args -- neither an owned capture (`bind::capture()` et al),+ // nor a parent's `capture`. Passed via forwarding reference.++ // This may be redundant, since `co_cleanup_capture` enforces that+ // cleanup types are immovable. If we did allow passing bare+ // `co_cleanup` types, it could violate memory safety protections for+ // `async_closure`s. For `async_now_closure`, there is also no obvious+ // use-case for passing `*captureVar` by-reference into the child.+ static_assert(+ !has_async_closure_co_cleanup<UncvrefST>,+ "This argument implements `async_closure` cleanup, so you should "+ "almost certainly pass it `bind::capture()` -- or, if you already "+ "have as a reference `capture`, by-value.");++ return async_closure_regular_arg<ST, decltype(bind_wrapper)>{+ .bindWrapper_ = std::move(bind_wrapper)};+ }+ }+ }+};++// See `vtag_safety_of_async_closure_args` for the docs.+// NB: As a nested lambda, this breaks on clang-17 due to compiler bugs.+template <bool ParentViewOfSafety, typename T>+auto vtag_safety_of_async_closure_arg() {+ // "owned capture": `store_as` outputs `async_closure_*_stored_arg`.+ if constexpr (+ is_async_closure_outer_stored_arg<T> ||+ is_instantiation_of_v<async_closure_inner_stored_arg, T>) {+ using CT = typename T::storage_type::capture_type;+ static_assert(!std::is_reference_v<CT>);+ // Stored captures are as safe as the type being stored. For example, when+ // a closure stores a `BackgroundTask<Safety, T>`, it cannot be safer than+ // `Safety`.+ //+ // While this replicates `lenient_safe_alias_of_v` logic, we don't directly+ // use it here, since `AsyncObject.h` specializes `capture_safety_impl_v`.+ return vtag<capture_safety_impl_v<CT, safe_alias::maybe_value>>;+ } else if constexpr ( //+ is_instantiation_of_v<async_closure_scope_self_ref_hack, T>) {+ // This is a closure made by `spawn_self_closure()` et al. It must:+ // - Avoid marking the closure's outer task `shared_cleanup`, so it can+ // still be added to the scope that made it (`if` branch).+ // - Downgrade [*] the safety of its own captures (`else` branch).+ //+ // [*] It would be memory-unsafe to reference such captures from+ // recursively scheduled closures on the same scope!+ if constexpr (ParentViewOfSafety) {+ return vtag<>;+ } else {+ constexpr auto storage_safety =+ lenient_safe_alias_of_v<typename T::storage_type>;+ // In current usage, ref_hack can only contain `co_cleanup_capture<V&>`.+ static_assert(storage_safety == safe_alias::shared_cleanup);+ return vtag<storage_safety>;+ }+ } else if constexpr (is_any_capture<T>) {+ // "pass capture ref": Output of the `to_capture_ref` branch.+ static_assert(std::is_reference_v<typename T::capture_type>);+ return vtag<lenient_safe_alias_of_v<T>>;+ } else if constexpr (std::is_same_v<capture_ref_measurement_stub, T>) {+ if constexpr (ParentViewOfSafety) {+ // Only allow capture-by-reference in `async_now_closure`s+ return vtag<safe_alias::unsafe>;+ } else {+ // But, don't do closure-internal downgrades, since `transform_bindings`+ // tries to prevent it from taking in refs to co_cleanup types this way.+ return vtag<>;+ }+ } else {+ // "regular arg": A non-`capture` passed via forwarding reference.+ static_assert(is_instantiation_of_v<async_closure_regular_arg, T>);+ return vtag<lenient_safe_alias_of_v<typename T::storage_type>>;+ }+}++// Returns a vtag of `safe_alias_v` for the storage type of the args that+// did not come from `store_capture_binding`.+//+// We have to special-case the stored ones because `Captures.h` marks the+// `capture` wrappers for on-closure stored values `unsafe` to discourage users+// from moving them from the original closure. And, the wrappers themselves+// check the safety of the underlying type (via `capture_safety`).+//+// The doc in `scheduleScopeClosure()` justifies why our first call to this+// function includes `ref_hack` args in the measurement (we want the+// closure's own args downgraded to `after_cleanup_ref` safety), but not in+// the second (we don't want the emitted `safe_task` to be knocked down to+// `shared_cleanup` safety, since that would make it unschedulable).+template <bool ParentViewOfSafety, typename TransformedBindingList>+constexpr auto vtag_safety_of_async_closure_args() {+ return []<typename... T>(tag_t<T...>) {+ return value_list_concat_t<+ vtag_t,+ decltype(vtag_safety_of_async_closure_arg<+ ParentViewOfSafety,+ T>())...>{};+ }(TransformedBindingList{});+}++template <typename BindingT>+constexpr bool capture_needs_outer_coro() {+ using BP = bind::ext::bind_to_storage_policy<BindingT>;+ using ST = typename BP::storage_type;+ return has_async_closure_co_cleanup<ST>;+}++struct async_closure_bindings_cfg {+ bool force_outer_coro;+ bool emit_now_task;+ bool is_invoke_member;+};++// For `is_invoke_member` closures, we must run an additional lifetime-safety+// check. For convenience, we also implicitly wrap the first argument with+// `bind::capture` when that's the obviously right choice.+template <async_closure_bindings_cfg Cfg>+struct async_closure_invoke_member_bindings {+ constexpr auto operator()(tag_t<>) { return tag<>; }+ template <auto BI0, typename BT0, auto... BI, typename... BT>+ constexpr auto operator()(+ tag_t<bind::ext::binding_t<BI0, BT0>, bind::ext::binding_t<BI, BT>...>) {+ using T = std::remove_cvref_t<BT0>;+ constexpr bool arg0_is_non_owning_ptr =+ // `transform_binding()` passes captures as non-owning refs+ is_any_capture<T> ||+ // Raw pointers are allowed in `async_now_closure()`+ std::is_pointer_v<T> ||+ is_instantiation_of_v<folly::coro::AsyncObjectNonSlotPtr, T> ||+ // `scheduleScopeClosure` & `scheduleSelfClosure` give non-owning+ // pointers. NB: This covers `SlotLimitedObjectPtr`.+ is_instantiation_of_v<async_closure_scope_self_ref_hack, T>;+ // Invoking a `member_task` requires `force_outer_coro` iff the first arg+ // is an owning capture.+ //+ // NB: Both implicit & explicit `bind::capture()`s are assumed to be owning,+ // and thus also `force_outer_coro`.+ //+ // The reason that `force_outer_coro` is NOT done automatically is that+ // it adds perf overhead, which would be easily avoided if the user made+ // their member function `static` instead.+ static_assert(+ Cfg.force_outer_coro || !Cfg.is_invoke_member || arg0_is_non_owning_ptr,+ "It looks like you want the `member_task` closure to own the object "+ "instance. Use `async_now_closure(bind::args{&obj}, fn)` if that "+ "applies. The next best approach is to make your task `static`, "+ "with its first arg `auto self`. If that's not viable, then use "+ "`async_closure_config{.force_outer_coro = true}` to allocate a "+ "coro frame to own your object.");+ // Syntax sugar: `bind::capture()` may be left as implicit for the arg0+ // "object parameter" of `FOLLY_INVOKE_MEMBER`.+ if constexpr (+ Cfg.is_invoke_member &&+ // If arg0 is `bind::capture()` or similar, don't double-wrap it.+ !std::derived_from<decltype(BI0), bind::detail::capture_bind_info_t> &&+ // Non-owning pointer-like things don't need to be captured.+ !arg0_is_non_owning_ptr) {+ static_assert(+ // BT0 is a value for `bind::in_place`, rval ref otherwise.+ !std::is_lvalue_reference_v<BT0>,+ "If you call `async_closure` with `FOLLY_INVOKE_MEMBER` and "+ "a non-`capture` argument, then it has to be an r-value, so "+ "that the closure can take ownership of the object instance. "+ "Consider `folly::copy()` or `std::move()`.");+ return tag<+ bind::ext::binding_t<+ bind::detail::capture_bind_info<+ bind::detail::capture_kind::plain>{}(BI0),+ BT0>,+ bind::ext::binding_t<BI, BT>...>;+ } else {+ return tag<+ bind::ext::binding_t<BI0, BT0>,+ bind::ext::binding_t<BI, BT>...>;+ }+ }+};++template <safe_alias ParentView, bool ArgsForceSharedCleanup>+struct async_closure_arg_safety {+ static inline constexpr auto parent_view = ParentView;+ static inline constexpr auto args_force_shared_cleanup =+ ArgsForceSharedCleanup;+};++// Converts forwarded arguments to bindings, figures out the storage policy+// (outer coro?, shared cleanup?), and applies `transform_bindings` to compute+// the final storage & binding outcome for each argument. The caller should+// create an outer coro iff the resulting `tuple` contains at least one+// `async_closure_outer_stored_arg`.+//+// Returns a 3-tuple:+// [0] `async_closure_arg_safety`, see `vtag_safety_of_async_closure_arg()`+// [1] A tuple `transform_bindings()` outputs, containing:+// binding OR async_closure_{inner,outer}_stored_arg, ...+// This tuple is always used by `bind_captures_to_closure`, at least for+// type computations, if not for creating the actual inner coro. It is+// computed as if the closure's shared-cleanup behavior were fully+// determined by the bound args we see here.+// [2] Either `nullptr`, OR a tuple of `transform_bindings()` outputs identical+// to [1], except that shared-cleanup is force-enabled. This tuple is only+// computed for the cases when `bind_captures_to_closure` would use it.+//+// NB: It's fine for this implementation detail to take `BoundArgs` by-ref+// because `async_closure` & friends took them by value.+template <async_closure_bindings_cfg Cfg, typename BoundArgs>+constexpr auto async_closure_safeties_and_bindings(BoundArgs&& bargs) {+ using Bindings = decltype(async_closure_invoke_member_bindings<Cfg>{}(+ typename BoundArgs::binding_list_t{}));++ auto tup = static_cast<BoundArgs&&>(bargs).unsafe_tuple_to_bind();+ auto make_bindings = [&]<binding_helper_cfg HelperCfg>(vtag_t<HelperCfg>) {+ return [&]<size_t... Is>(std::index_sequence<Is...>) {+ return lite_tuple::tuple{[&]() {+ using Binding = type_list_element_t<Is, Bindings>;+ using T = std::tuple_element_t<Is, decltype(tup)>;+ return capture_binding_helper<Binding, HelperCfg, Is>::+ transform_binding(+ bind_wrapper_t<T>{+ .t_ = static_cast<T&&>(lite_tuple::get<Is>(tup))});+ }()...};+ }(std::make_index_sequence<type_list_size_v<Bindings>>{});+ };++ // Future: If there are many `bind::in_place` arguments (which require+ // `capture_heap`), it may be more efficient to auto-select an outer coro,+ // for just 2 heap allocations. Beware: this changes user-facing types+ // (`capture_heap` to `capture`), but most users shouldn't depend on that.+ constexpr bool has_outer_coro =+ Cfg.force_outer_coro || []<typename... Bs>(tag_t<Bs...>) {+ return (capture_needs_outer_coro<Bs>() || ...);+ }(Bindings{});+ // Figure out `IsSharedCleanupClosure` for `binding_cfg` for the real+ // `transform_binding` call.+ //+ // Our choice of `is_shared_cleanup_closure = true` is important since we+ // reuse this type list for the returned+ // `vtag_safety_of_async_closure_args`. That vtag is used by+ // `async_closure` to compute the safety level for the resulting+ // `safe_task`. This safety must NOT be increased by reference upgrades --+ // a reference's safety is only upgraded inside the child closure, but the+ // original safety applies in the parent closure, which is where the+ // returned `vtag` is consumed.+ //+ // Choosing `true` here does not affect the `is_shared_cleanup` choice below+ // It merely toggles between `after_cleanup_ref_capture` and `capture`, with+ // either `after_cleanup_ref` or `co_cleanup_safe_ref` safety.+ using shared_cleanup_transformed_binding_types = type_list_concat_t<+ tag_t,+ decltype(make_bindings(+ vtag<binding_helper_cfg{+ .is_shared_cleanup_closure = true,+ .has_outer_coro = has_outer_coro,+ .in_safety_measurement_pass = true}>))>;+ // The template args store two views of the safety of the closure's args:+ // - As seen by the parent, where `ParentViewOfSafety == true`,+ // - As used for internal safety computations.+ //+ // This toggle supports two usage scenarios:+ //+ // (1) Capture-by-reference behaviors, like `bind::capture_const_ref()` /+ // `bind::capture(const_ref())` et al.+ // - `unsafe` for parent -- Since these are raw references from the+ // parent's scope, ensure they're only allowed in `async_now_closure`s.+ // - Ignored by child -- Simultaneously, we don't want the internal coro+ // to be subject to shared-cleanup downgrades. Doing that would,+ // e.g., break the useful pattern of an on-closure scope collecting+ // results on a parent collector passed via capture-by-reference.+ //+ // (2) Closures created by `spawn_self_closure()` et al. Also see+ // `async_closure_scope_self_ref_hack`.+ //+ // - In the returned `vtag` that measures the parent's view of the safety+ // of the closure, `ParentViewOfSafety == true` will exclude the+ // closure's first arg (the scope or object ref) from the vtag -- it+ // would otherwise be `shared_cleanup`. That is, of course, the entire+ // point of `spawn_self_closure()` -- we happen to know that the scope+ // ref is safe because of the circumstances of the closure's creation.+ //+ // - Using `ParentViewOfSafety = false` here makes `spawn_self_closure`s+ // **internally** consider themselves to be `shared_cleanup` closures.+ // I.e. `after_cleanup_` inputs are not upgraded, and owned captures are+ // downgraded to `after_cleanup_`.+ //+ // To see that these downgrades are the correct behavior, imagine a chain+ // of closures, each calling `spawn_self_closure()` to make the next.+ // `safe_async_scope` awaits these concurrently, so they must not take+ // dependencies on each other's owned captures.+ async_closure_arg_safety<+ // parent_view=+ vtag_least_safe_alias(+ vtag_safety_of_async_closure_args<+ /*ParentViewOfSafety*/ true,+ shared_cleanup_transformed_binding_types>()),+ // args_force_shared_cleanup=+ (safe_alias::shared_cleanup >=+ vtag_least_safe_alias(+ vtag_safety_of_async_closure_args<+ /*ParentViewOfSafety*/ false,+ shared_cleanup_transformed_binding_types>()))>+ arg_min_safety;++ // How this async-closure will store and/or bind its arguments depends on+ // whether we have to force shared-cleanup downgrades for its args.+ //+ // This can happen in two ways:+ //+ // (1) `async_closure` takes a `safe_task` and emits a `safe_task`. Just+ // above, we computed` `args_force_shared_cleanup`, which says that the+ // closure needs shared-cleanup iff a parent passes a+ // `co_cleanup_capture` ref.+ static_assert(+ safe_alias::closure_min_arg_safety == safe_alias::shared_cleanup);++ // (2) `async_now_closure` always emits a `now_task`, which is meant+ // to be usable even with unsafe coros & arguments.+ //+ // However, we only need shared-cleanup if the inner-coro COULD access a+ // ref of safety <= `shared_cleanup`. Above, we tested the explicit+ // inputs via `args_force_shared_cleanup`, but that misses the "implicit+ // object parameter" (aka `this`) of the coro callable.+ //+ // For example, `async_now_closure(..., [&]() -> now_task<> { ... })`+ // compiles, and can access potentially-unsafe refs via the lambda's+ // `this` pointer, which gets access to raw refs into the outer scope.+ //+ // Since `make_inner_coro` is evaluated synchronously, it is allowed to+ // be a "coro wrapper", i.e. a plain lambda with unsafe captures that+ // calls an actual coroutine function. We only care about the safety of+ // the resulting coro. Luckily, `is_safe_task_valid` from `SafeTask.h`+ // constrains the safety of the implicit object parameter. Specifically,+ // we know that a `safe_task` with `ArgSafety >= unsafe_internal_closure`+ // requires[*] the implicit object parameter to have safety `>+ // shared_cleanup` (yes, greater than!), which is exactly our goal here.+ //+ // To sum up: Above, we saw that if `async_now_closure`'s inner coro has+ // safety `>= unsafe_closure_internal`, then it should not have access to+ // any refs of safety `<= shared_cleanup`.+ //+ // HOWEVER, here, in `async_closure_safeties_and_bindings`, we do not yet+ // know the inner coro result type, and cannot test its safety. In fact,+ // we need to synthesize the argument types for `make_inner_coro` just to+ // resolve the right overload. As a workaround, the returned 3-tuple+ // provides bindings for BOTH options -- by tuple index:+ //+ // [1] `task_forces_shared_cleanup == false`. Use these arg bindings,+ // made with `.is_shared_cleanup == args_force_shared_cleanup`, to+ // compute the inner coro type. Use them to actually make the inner+ // coro ONLY IF it has safety `>= unsafe_closure_internal`.+ //+ // [2] `task_forces_shared_cleanup == true`. These arg bindings are+ // computed optionally (condition below), by setting+ // `.is_shared_cleanup == true`. They will be used to make a+ // fallback inner coro if the plan from [1] didn't work. We force+ // shared cleanup because, per above, the unsafe inner coro may have+ // access to unsafe refs.+ auto maybe_make_bindings_for =+ [&]<bool task_forces_shared_cleanup>(vtag_t<task_forces_shared_cleanup>) {+ if constexpr (+ // Always run `make_bindings` for index 1 of the returned `tuple`.+ !task_forces_shared_cleanup ||+ // Only run `make_bindings` for index 2 IF it may get used.+ (Cfg.emit_now_task && !arg_min_safety.args_force_shared_cleanup)) {+ return make_bindings(+ vtag<binding_helper_cfg{+ .is_shared_cleanup_closure = task_forces_shared_cleanup ||+ arg_min_safety.args_force_shared_cleanup,+ .has_outer_coro = has_outer_coro,+ .in_safety_measurement_pass = false}>);+ } else {+ return nullptr; // result[2] will not be used+ }+ };++ return lite_tuple::tuple{+ arg_min_safety,+ // The doc above `maybe_make_bindings_for` explains how these are used.+ maybe_make_bindings_for(vtag</*task_forces_shared_cleanup*/ false>),+ maybe_make_bindings_for(vtag</*task_forces_shared_cleanup*/ true>)};+}++} // namespace folly::coro::detail++FOLLY_POP_WARNING+#endif
@@ -26,7 +26,6 @@ #include <folly/synchronization/RWSpinLock.h> #include <folly/debugging/exception_tracer/Compatibility.h>-#include <folly/debugging/exception_tracer/ExceptionTracerLib.h> #include <folly/debugging/exception_tracer/StackTrace.h> #include <folly/experimental/symbolizer/Symbolizer.h> @@ -101,16 +100,12 @@ return out; } -} // namespace exception_tracer-} // namespace folly--namespace {- /* * This handler gathers statistics on all exceptions thrown by the program * Information is being stored in thread local storage. */-void throwHandler(void*, std::type_info* exType, void (**)(void*)) noexcept {+void exceptionStatsThrowHandler(+ void*, std::type_info* exType, void (**)(void*)) noexcept { // This array contains the exception type and the stack frame // pointers so they get all hashed together. uintptr_t frames[kMaxFrames + 1];@@ -139,13 +134,8 @@ }); } -struct Initializer {- Initializer() { registerCxaThrowCallback(throwHandler); }-};--Initializer initializer;--} // namespace+} // namespace exception_tracer+} // namespace folly #endif // FOLLY_HAS_EXCEPTION_TRACER
@@ -35,6 +35,13 @@ }; /**+ * Throw handler that intercepts exception throwing and accumulates stats.+ * Use with registerCxaThrowCallback/unregisterCxaThrowCallback.+ */+void exceptionStatsThrowHandler(+ void*, std::type_info* exType, void (**)(void*)) noexcept;++/** * This function accumulates exception throwing statistics across all threads. * Please note, that during call to this function, other threads might block * on exception throws, so it should be called seldomly.
@@ -0,0 +1,35 @@+/*+ * Copyright (c) Meta Platforms, Inc. and affiliates.+ *+ * Licensed under the Apache License, Version 2.0 (the "License");+ * you may not use this file except in compliance with the License.+ * You may obtain a copy of the License at+ *+ * http://www.apache.org/licenses/LICENSE-2.0+ *+ * Unless required by applicable law or agreed to in writing, software+ * distributed under the License is distributed on an "AS IS" BASIS,+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.+ * See the License for the specific language governing permissions and+ * limitations under the License.+ */++#include <folly/debugging/exception_tracer/ExceptionCounterLib.h>+#include <folly/debugging/exception_tracer/ExceptionTracerLib.h>++#if FOLLY_HAVE_ELF && FOLLY_HAVE_DWARF && FOLLY_HAS_EXCEPTION_TRACER++namespace {++struct Initializer {+ Initializer() {+ folly::exception_tracer::registerCxaThrowCallback(+ folly::exception_tracer::exceptionStatsThrowHandler);+ }+};++Initializer initializer;++} // namespace++#endif // FOLLY_HAVE_ELF && FOLLY_HAVE_DWARF && FOLLY_HAS_EXCEPTION_TRACER
@@ -22,7 +22,6 @@ #include <folly/debugging/exception_tracer/ExceptionTracer.h> #include <folly/debugging/exception_tracer/ExceptionTracerLib.h> #include <folly/debugging/exception_tracer/StackTrace.h>-#include <folly/experimental/symbolizer/Symbolizer.h> #if FOLLY_HAVE_ELF && FOLLY_HAVE_DWARF @@ -47,13 +46,46 @@ } // namespace // These functions are exported and may be found via dlsym(RTLD_NEXT, ...)-extern "C" const StackTraceStack* getUncaughtExceptionStackTraceStack() {+extern "C" const StackTraceStack*+folly_exception_tracer_get_uncaught_exceptions_stack_trace_stack() { return invalid ? nullptr : &uncaughtExceptions; }-extern "C" const StackTraceStack* getCaughtExceptionStackTraceStack() {+extern "C" const StackTraceStack*+folly_exception_tracer_get_caught_exceptions_stack_trace_stack() { return invalid ? nullptr : &caughtExceptions; } +namespace folly {++namespace detail {+/// access_cxxabi+///+/// When building folly with thread sanitizer but using a prebuilt libstdc++ or+/// libc++, folly's accesses here are instrumented while the standard library's+/// accesses are not. This can cause false positive data-race reports. This+/// namespace is here as an easy way to set up thread-sanitizer suppressions.+///+/// One scenario is when the __cxa_end_catch folly hook does an access, then the+/// last std::exception_ptr to the same exception is released. The exception_ptr+/// tor does an uninstrumented access and then calls free, which like malloc is+/// instrumented. The thread-sanitizer runtime observes only an access in folly+/// in one thread followed by a call to free in another thread, but does not+/// observe the synchronizing refcount-decrement in between.+///+/// Aside from suppressions, there are two other solutions:+/// * Never access cxxabi data directly.+/// * Use an instrumented build of the standard library when building folly or+/// the application with instrumentation.+namespace access_cxxabi {++static auto get_caught_exceptions_handler_count() {+ return __cxxabiv1::__cxa_get_globals_fast()->caughtExceptions->handlerCount;+}++} // namespace access_cxxabi++} // namespace detail+ namespace { void addActiveException() {@@ -98,8 +130,8 @@ return; } - __cxxabiv1::__cxa_exception* top =- __cxxabiv1::__cxa_get_globals_fast()->caughtExceptions;+ auto topHandlerCount =+ detail::access_cxxabi::get_caught_exceptions_handler_count(); // This is gcc specific and not specified in the ABI: // abs(handlerCount) is the number of active handlers, it's negative // for rethrown exceptions and positive (always 1) for regular@@ -107,7 +139,7 @@ // In the rethrow case, we've already popped the exception off the // caught stack, so we don't do anything here. // For Lua interop, we see the handlerCount = 0- if ((top->handlerCount == 1) || (top->handlerCount == 0)) {+ if ((topHandlerCount == 1) || (topHandlerCount == 0)) { if (!caughtExceptions.pop()) { uncaughtExceptions.clear(); invalid = true;@@ -129,6 +161,8 @@ Initializer initializer; } // namespace++} // namespace folly #endif // FOLLY_HAS_EXCEPTION_TRACER
@@ -22,7 +22,6 @@ #include <glog/logging.h> -#include <folly/CppAttributes.h> #include <folly/Portability.h> #include <folly/String.h> #include <folly/debugging/exception_tracer/ExceptionAbi.h>@@ -44,7 +43,8 @@ using namespace __cxxabiv1; extern "C" {-const StackTraceStack* getCaughtExceptionStackTraceStack(void)+const StackTraceStack*+folly_exception_tracer_get_caught_exceptions_stack_trace_stack(void) __attribute__((__weak__)); typedef const StackTraceStack* (*GetCaughtExceptionStackTraceStackType)(); GetCaughtExceptionStackTraceStackType getCaughtExceptionStackTraceStackFn;@@ -138,14 +138,18 @@ std::vector<ExceptionInfo> getCurrentExceptions() { struct Once { Once() {- // See if linked in with us (getCaughtExceptionStackTraceStack is weak)- getCaughtExceptionStackTraceStackFn = getCaughtExceptionStackTraceStack;+ // See if linked in with us+ // (folly_exception_tracer_get_caught_exceptions_stack_trace_stack is+ // weak)+ getCaughtExceptionStackTraceStackFn =+ folly_exception_tracer_get_caught_exceptions_stack_trace_stack; if (!getCaughtExceptionStackTraceStackFn) { // Nope, see if it's in a shared library getCaughtExceptionStackTraceStackFn = (GetCaughtExceptionStackTraceStackType)dlsym(- RTLD_NEXT, "getCaughtExceptionStackTraceStack");+ RTLD_NEXT,+ "folly_exception_tracer_get_caught_exceptions_stack_trace_stack"); } } };
@@ -32,7 +32,7 @@ namespace __cxxabiv1 { extern "C" {-#ifdef FOLLY_STATIC_LIBSTDCXX+#ifdef FOLLY_INSTALL_STATIC_CXA_FUNCTIONS_WRAPPERS [[noreturn]] void __real___cxa_throw( void* thrownException, std::type_info* type, void (*destructor)(void*)); void* __real___cxa_begin_catch(void* excObj) noexcept;@@ -49,7 +49,7 @@ } // namespace __cxxabiv1 -#ifdef FOLLY_STATIC_LIBSTDCXX+#ifdef FOLLY_INSTALL_STD_RETHROW_EXCEPTION_WRAPPER extern "C" { #if __GLIBCXX__ [[noreturn]] void@@ -73,6 +73,10 @@ class CallbackHolder { public: void registerCallback(Sig& f) { callbacks_.wlock()->push_back(f); }+ void unregisterCallback(Sig& f) {+ auto callbacks = callbacks_.wlock();+ std::erase(*callbacks, f);+ } // always inline to enforce kInternalFramesNumber template <typename... Args>@@ -99,6 +103,9 @@ } \ void register##NAME##Callback(NAME##Sig& callback) { \ get##NAME##Callbacks().registerCallback(callback); \+ } \+ void unregister##NAME##Callback(NAME##Sig& callback) { \+ get##NAME##Callbacks().unregisterCallback(callback); \ } FOLLY_EXNTRACE_DECLARE_CALLBACK(CxaThrow)@@ -114,7 +121,7 @@ namespace __cxxabiv1 { -#ifdef FOLLY_STATIC_LIBSTDCXX+#ifdef FOLLY_INSTALL_STATIC_CXA_FUNCTIONS_WRAPPERS extern "C" { [[noreturn]] void __wrap___cxa_throw(@@ -190,7 +197,7 @@ } // namespace __cxxabiv1 -#ifdef FOLLY_STATIC_LIBSTDCXX+#ifdef FOLLY_INSTALL_STD_RETHROW_EXCEPTION_WRAPPER // Mangled name for std::rethrow_exception // TODO(tudorb): Dicey, as it relies on the fact that std::exception_ptr // is typedef'ed to a type in namespace __exception_ptr
@@ -37,6 +37,11 @@ void registerCxaRethrowCallback(CxaRethrowSig& callback); void registerCxaEndCatchCallback(CxaEndCatchSig& callback); void registerRethrowExceptionCallback(RethrowExceptionSig& callback);+void unregisterCxaThrowCallback(CxaThrowSig& callback);+void unregisterCxaBeginCatchCallback(CxaBeginCatchSig& callback);+void unregisterCxaRethrowCallback(CxaRethrowSig& callback);+void unregisterCxaEndCatchCallback(CxaEndCatchSig& callback);+void unregisterRethrowExceptionCallback(RethrowExceptionSig& callback); } // namespace exception_tracer } // namespace folly
@@ -27,15 +27,16 @@ namespace { void metaDeleter(void* ex) noexcept {- auto syncMeta = detail::getMetaMap().withWLock([ex](auto& locked) noexcept {+ auto meta = detail::getMetaMap().withWLock([ex](auto& locked) noexcept { auto iter = locked.find(ex);+ CHECK(iter != locked.end()); auto ret = std::move(iter->second); locked.erase(iter); return ret; }); // If the thrown object was allocated statically it may not have a deleter.- auto meta = syncMeta->wlock();+ CHECK(meta); if (meta->deleter) { meta->deleter(ex); }@@ -57,31 +58,28 @@ }; handlingThrow = true; - // This can allocate memory potentially causing problems in an OOM- // situation so we catch and short circuit. try {- auto newMeta = std::make_unique<detail::SynchronizedExceptionMeta>();- newMeta->withWLock([&deleter](auto& lockedMeta) {- // Override the deleter with our custom one and capture the old one.- lockedMeta.deleter = std::exchange(*deleter, metaDeleter);+ // This can allocate memory potentially causing problems in an OOM+ // situation so we catch and short circuit.+ auto meta = std::make_shared<detail::ExceptionMeta>(); - ssize_t n = folly::symbolizer::getStackTrace(- lockedMeta.trace.addresses, kMaxFrames);- if (n != -1) {- lockedMeta.trace.frameCount = n;- }- ssize_t nAsync = folly::symbolizer::getAsyncStackTraceSafe(- lockedMeta.traceAsync.addresses, kMaxFrames);- if (nAsync != -1) {- lockedMeta.traceAsync.frameCount = nAsync;- }- });+ // Override the deleter with our custom one and capture the old one.+ meta->deleter = std::exchange(*deleter, metaDeleter); - auto oldMeta = detail::getMetaMap().withWLock([ex, &newMeta](auto& wlock) {- return std::exchange(wlock[ex], std::move(newMeta));- });- DCHECK(oldMeta == nullptr);+ ssize_t n =+ folly::symbolizer::getStackTrace(meta->trace.addresses, kMaxFrames);+ if (n != -1) {+ meta->trace.frameCount = n;+ }+ ssize_t nAsync = folly::symbolizer::getAsyncStackTraceSafe(+ meta->traceAsync.addresses, kMaxFrames);+ if (nAsync != -1) {+ meta->traceAsync.frameCount = nAsync;+ } + detail::getMetaMap().withWLock([&](auto& wlock) {+ CHECK(wlock.try_emplace(ex, std::move(meta)).second);+ }); } catch (const std::bad_alloc&) { } }
@@ -24,7 +24,6 @@ #include <folly/debugging/exception_tracer/ExceptionTracerLib.h> #include <folly/debugging/exception_tracer/SmartExceptionTracerSingleton.h> #include <folly/debugging/exception_tracer/StackTrace.h>-#include <folly/experimental/symbolizer/Symbolizer.h> #include <folly/lang/Exception.h> #if FOLLY_HAVE_ELF && FOLLY_HAVE_DWARF@@ -42,7 +41,7 @@ // the stack frames to use. template <typename ExceptionMetaFunc> ExceptionInfo getTraceWithFunc(- const std::exception& ex, ExceptionMetaFunc func) {+ void* ex, const std::type_info* typeInfo, ExceptionMetaFunc func) { if (!detail::isSmartExceptionTracerHookEnabled() && !loggedMessage.load(std::memory_order_relaxed)) { LOG(WARNING)@@ -51,57 +50,49 @@ } ExceptionInfo info;- info.type = &typeid(ex);- auto rlockedMeta = detail::getMetaMap().withRLock(- [&](const auto& locked) noexcept- -> detail::SynchronizedExceptionMeta::RLockedPtr {- auto* meta = get_ptr(locked, (void*)&ex);- // If we can't find the exception, return an empty stack trace.- if (!meta) {- return {};- }- CHECK(*meta);- // Acquire the meta rlock while holding the map's rlock, to block meta's- // destruction.- return (*meta)->rlock();- });+ info.type = typeInfo; - if (!rlockedMeta) {- return info;+ if (auto meta = get_default(*detail::getMetaMap().rlock(), ex)) {+ auto [traceBeginIt, traceEndIt] = func(*meta);+ info.frames.assign(traceBeginIt, traceEndIt); } - auto [traceBeginIt, traceEndIt] = func(*rlockedMeta);- info.frames.assign(traceBeginIt, traceEndIt); return info; } template <typename ExceptionMetaFunc> ExceptionInfo getTraceWithFunc( const std::exception_ptr& ptr, ExceptionMetaFunc func) {- if (auto* ex = folly::exception_ptr_get_object<std::exception>(ptr)) {- return getTraceWithFunc(*ex, std::move(func));+ if (auto ex = folly::exception_ptr_get_object(ptr, nullptr)) {+ return getTraceWithFunc(+ ex, folly::exception_ptr_get_type(ptr), std::move(func)); } return ExceptionInfo(); } template <typename ExceptionMetaFunc> ExceptionInfo getTraceWithFunc(+ const std::exception& e, ExceptionMetaFunc func) {+ return getTraceWithFunc(+ const_cast<void*>(dynamic_cast<const void*>(&e)),+ &typeid(e),+ std::move(func));+}++template <typename ExceptionMetaFunc>+ExceptionInfo getTraceWithFunc( const exception_wrapper& ew, ExceptionMetaFunc func) {- if (auto* ex = ew.get_exception()) {- return getTraceWithFunc(*ex, std::move(func));- }- return ExceptionInfo();+ return getTraceWithFunc(ew.exception_ptr(), std::move(func)); } auto getAsyncStackTraceItPair(const detail::ExceptionMeta& meta) {- return std::make_pair(- meta.traceAsync.addresses,- meta.traceAsync.addresses + meta.traceAsync.frameCount);+ auto addr = meta.traceAsync.addresses;+ return std::pair(addr, addr + meta.traceAsync.frameCount); } auto getNormalStackTraceItPair(const detail::ExceptionMeta& meta) {- return std::make_pair(- meta.trace.addresses, meta.trace.addresses + meta.trace.frameCount);+ auto addr = meta.trace.addresses;+ return std::pair(addr, addr + meta.trace.frameCount); } } // namespace
@@ -30,10 +30,11 @@ StackTrace traceAsync; }; -using SynchronizedExceptionMeta = folly::Synchronized<ExceptionMeta>;-+// using a vector-map so that all the values are laid out contiguously in an+// array, specifically to make debugging easier - no need to learn the structure+// of the f14-chunk-array when debugging the exception hooks using ExceptionMetaMap =- folly::F14VectorMap<void*, std::unique_ptr<SynchronizedExceptionMeta>>;+ folly::F14VectorMap<void const*, std::shared_ptr<ExceptionMeta const>>; Synchronized<ExceptionMetaMap>& getMetaMap();
@@ -27,7 +27,7 @@ #if FOLLY_HAVE_DWARF && FOLLY_HAVE_ELF -#include <dwarf.h> // @manual=fbsource//third-party/libdwarf:dwarf+#include <dwarf.h> // @manual namespace folly { namespace symbolizer {
@@ -21,9 +21,9 @@ #include <folly/Function.h> #include <folly/Range.h> #include <folly/debugging/symbolizer/DwarfUtil.h>-#include <folly/experimental/symbolizer/Elf.h>-#include <folly/experimental/symbolizer/ElfCache.h>-#include <folly/experimental/symbolizer/SymbolizedFrame.h>+#include <folly/debugging/symbolizer/Elf.h>+#include <folly/debugging/symbolizer/ElfCache.h>+#include <folly/debugging/symbolizer/SymbolizedFrame.h> namespace folly { namespace symbolizer {
@@ -27,7 +27,7 @@ #if FOLLY_HAVE_DWARF && FOLLY_HAVE_ELF -#include <dwarf.h> // @manual=fbsource//third-party/libdwarf:dwarf+#include <dwarf.h> // @manual // We need a single dwarf5 tag, but may not be building against // a new enough libdwarf, so just define it ourselves.
@@ -25,9 +25,9 @@ #include <folly/debugging/symbolizer/DwarfLineNumberVM.h> #include <folly/debugging/symbolizer/DwarfSection.h> #include <folly/debugging/symbolizer/DwarfUtil.h>-#include <folly/experimental/symbolizer/Elf.h>-#include <folly/experimental/symbolizer/ElfCache.h>-#include <folly/experimental/symbolizer/SymbolizedFrame.h>+#include <folly/debugging/symbolizer/Elf.h>+#include <folly/debugging/symbolizer/ElfCache.h>+#include <folly/debugging/symbolizer/SymbolizedFrame.h> namespace folly { namespace symbolizer {
@@ -18,7 +18,7 @@ #include <folly/Range.h> #include <folly/debugging/symbolizer/DwarfUtil.h>-#include <folly/experimental/symbolizer/SymbolizedFrame.h>+#include <folly/debugging/symbolizer/SymbolizedFrame.h> namespace folly { namespace symbolizer {
@@ -21,13 +21,13 @@ #include <folly/Optional.h> #include <folly/Range.h>-#include <folly/experimental/symbolizer/Elf.h>+#include <folly/debugging/symbolizer/Elf.h> #include <folly/portability/Config.h> #include <folly/portability/Unistd.h> #if FOLLY_HAVE_DWARF && FOLLY_HAVE_ELF -#include <dwarf.h> // @manual=fbsource//third-party/libdwarf:dwarf+#include <dwarf.h> // @manual // We need a single dwarf5 tag, but may not be building against // a new enough libdwarf, so just define it ourselves.
@@ -22,11 +22,11 @@ #include <folly/Function.h> #include <folly/Range.h>-#include <folly/experimental/symbolizer/Elf.h>-#include <folly/experimental/symbolizer/ElfCache.h>+#include <folly/debugging/symbolizer/Elf.h>+#include <folly/debugging/symbolizer/ElfCache.h> #if FOLLY_HAVE_DWARF && FOLLY_HAVE_ELF-#include <dwarf.h> // @manual=fbsource//third-party/libdwarf:dwarf+#include <dwarf.h> // @manual namespace folly { namespace symbolizer {
@@ -19,11 +19,20 @@ template <class Fn> const ElfPhdr* ElfFile::iterateProgramHeaders(Fn fn) const- noexcept(is_nothrow_invocable_v<Fn, ElfPhdr const&>) {- // there exist ELF binaries which execute correctly, but have invalid internal- // offset(s) to program/section headers; most probably due to invalid- // stripping of symbols- if (elfHeader().e_phoff + sizeof(ElfPhdr) >= length_) {+ noexcept(is_nothrow_invocable_v<Fn&, ElfPhdr const&>) {+ // Validate that all program headers fit within the mapped file bounds.+ // There exist malformed ELF binaries with invalid header counts/offsets,+ // which can cause SIGBUS when iterating past the valid mapped memory+ // region. This code must handle malformed binaries without crashing.+ size_t headersSize;+ size_t headersEnd;+ if (!folly::checked_mul(+ &headersSize,+ static_cast<size_t>(elfHeader().e_phnum),+ sizeof(ElfPhdr)) ||+ !folly::checked_add(+ &headersEnd, static_cast<size_t>(elfHeader().e_phoff), headersSize) ||+ headersEnd > length_) { return nullptr; } @@ -38,11 +47,22 @@ template <class Fn> const ElfShdr* ElfFile::iterateSections(Fn fn) const- noexcept(is_nothrow_invocable_v<Fn, ElfShdr const&>) {- // there exist ELF binaries which execute correctly, but have invalid internal- // offset(s) to program/section headers; most probably due to invalid- // stripping of symbols- if (elfHeader().e_shoff + sizeof(ElfShdr) >= length_) {+ noexcept(is_nothrow_invocable_v<Fn&, ElfShdr const&>) {+ // Validate that all section headers fit within the mapped file bounds.+ // There exist malformed ELF binaries with invalid header counts/offsets,+ // which can cause SIGBUS when iterating past the valid mapped memory+ // region. This code must handle malformed binaries without crashing.+ size_t sectionsSize;+ size_t sectionsEnd;+ if (!folly::checked_mul(+ §ionsSize,+ static_cast<size_t>(elfHeader().e_shnum),+ sizeof(ElfShdr)) ||+ !folly::checked_add(+ §ionsEnd,+ static_cast<size_t>(elfHeader().e_shoff),+ sectionsSize) ||+ sectionsEnd > length_) { return nullptr; } @@ -57,7 +77,7 @@ template <class Fn> const ElfShdr* ElfFile::iterateSectionsWithType(uint32_t type, Fn fn) const- noexcept(is_nothrow_invocable_v<Fn, ElfShdr const&>) {+ noexcept(is_nothrow_invocable_v<Fn&, ElfShdr const&>) { return iterateSections([&](const ElfShdr& sh) { return sh.sh_type == type && fn(sh); });@@ -66,7 +86,7 @@ template <class Fn> const ElfShdr* ElfFile::iterateSectionsWithTypes( std::initializer_list<uint32_t> types, Fn fn) const- noexcept(is_nothrow_invocable_v<Fn, ElfShdr const&>) {+ noexcept(is_nothrow_invocable_v<Fn&, ElfShdr const&>) { return iterateSections([&](const ElfShdr& sh) { auto const it = std::find(types.begin(), types.end(), sh.sh_type); return it != types.end() && fn(sh);@@ -75,7 +95,7 @@ template <class Fn> const char* ElfFile::iterateStrings(const ElfShdr& stringTable, Fn fn) const- noexcept(is_nothrow_invocable_v<Fn, const char*>) {+ noexcept(is_nothrow_invocable_v<Fn&, const char*>) { validateStringTable(stringTable); const char* start = file_ + stringTable.sh_offset;@@ -96,6 +116,19 @@ FOLLY_SAFE_CHECK( section.sh_entsize == sizeof(E), "invalid entry size in table"); + // Validate that the entire section data is within the mapped file bounds.+ // There exist malformed ELF binaries with invalid section offsets/sizes,+ // which can cause SIGBUS when iterating past the valid mapped memory+ // region. This code must handle malformed binaries without crashing.+ size_t sectionEnd;+ if (!folly::checked_add(+ §ionEnd,+ static_cast<size_t>(section.sh_offset),+ static_cast<size_t>(section.sh_size)) ||+ sectionEnd > length_) {+ return nullptr;+ }+ const E* ent = &at<E>(section.sh_offset); const E* end = ent + (section.sh_size / section.sh_entsize); @@ -112,14 +145,14 @@ template <class Fn> const ElfSym* ElfFile::iterateSymbols(const ElfShdr& section, Fn fn) const- noexcept(is_nothrow_invocable_v<Fn, ElfSym const&>) {+ noexcept(is_nothrow_invocable_v<Fn&, ElfSym const&>) { return iterateSectionEntries<ElfSym>(section, fn); } template <class Fn> const ElfSym* ElfFile::iterateSymbolsWithType( const ElfShdr& section, uint32_t type, Fn fn) const- noexcept(is_nothrow_invocable_v<Fn, ElfSym const&>) {+ noexcept(is_nothrow_invocable_v<Fn&, ElfSym const&>) { // N.B. st_info has the same representation on 32- and 64-bit platforms return iterateSymbols(section, [&](const ElfSym& sym) -> bool { return ELF32_ST_TYPE(sym.st_info) == type && fn(sym);@@ -129,13 +162,100 @@ template <class Fn> const ElfSym* ElfFile::iterateSymbolsWithTypes( const ElfShdr& section, std::initializer_list<uint32_t> types, Fn fn) const- noexcept(is_nothrow_invocable_v<Fn, ElfSym const&>) {+ noexcept(is_nothrow_invocable_v<Fn&, ElfSym const&>) { // N.B. st_info has the same representation on 32- and 64-bit platforms return iterateSymbols(section, [&](const ElfSym& sym) -> bool { auto const elfType = ELF32_ST_TYPE(sym.st_info); auto const it = std::find(types.begin(), types.end(), elfType); return it != types.end() && fn(sym); });+}++template <class Fn>+folly::Expected<ElfFile::Note, ElfFile::FindNoteError>+ElfFile::iterateNotesInBodyHelper(folly::StringPiece body, Fn& fn) const+ noexcept(is_nothrow_invocable_v<Fn&, const Note&>) {+ static_assert(alignof(ElfNhdr) >= 4);+ if (uintptr_t(body.data()) % alignof(ElfNhdr) != 0) {+ return Unexpected(FindNoteError(FindNoteFailureCode::NoteUnaligned));+ }++ while (body.size() > 0) {+ folly::span<const uint8_t> noteBody =+ span(reinterpret_cast<const uint8_t*>(body.data()), body.size());+ auto noteMaybe = Note::parse(noteBody);+ if (!noteMaybe) {+ return noteMaybe;+ }++ auto note = *noteMaybe;++ if (fn(note)) {+ return note;+ }++ body.advance(note.alignedSize());+ }++ return Unexpected(FindNoteError(FindNoteFailureCode::NoteNotFound));+}++template <class Fn>+folly::Expected<ElfFile::Note, ElfFile::FindNoteError>+ElfFile::iterateNotesInSections(const ElfShdr* section, Fn fn) const+ noexcept(is_nothrow_invocable_v<Fn&, const Note&>) {+ if (section != nullptr) {+ return iterateNotesInBodyHelper(getSectionBody(*section), fn);+ }++ folly::Expected<Note, FindNoteError> noteMaybe =+ Unexpected(FindNoteError(FindNoteFailureCode::NoteNotFound));+ iterateSectionsWithType(SHT_NOTE, [&](const ElfShdr& sh) {+ noteMaybe = iterateNotesInBodyHelper(getSectionBody(sh), fn);+ // Check if we got a good result, if so return it+ if (noteMaybe) {+ return STOP;+ }++ // Check if we got a data corruption error, stop and return that.+ if (noteMaybe.error().isDataCorruptionError()) {+ return STOP;+ }+ return CONTINUE;+ });++ return noteMaybe;+}++template <class Fn>+folly::Expected<ElfFile::Note, ElfFile::FindNoteError>+ElfFile::iterateNotesInSegments(const ElfPhdr* segment, Fn fn) const+ noexcept(is_nothrow_invocable_v<Fn&, const Note&>) {+ if (segment != nullptr) {+ return iterateNotesInBodyHelper(getSegmentBody(*segment), fn);+ }++ folly::Expected<Note, FindNoteError> noteMaybe =+ Unexpected(FindNoteError(FindNoteFailureCode::NoteNotFound));+ iterateProgramHeaders([&](const ElfPhdr& ph) {+ if (ph.p_type != PT_NOTE) {+ return CONTINUE;+ }++ noteMaybe = iterateNotesInBodyHelper(getSegmentBody(ph), fn);+ // Check if we got a good result, if so return it+ if (noteMaybe) {+ return STOP;+ }++ // Check if we got a data corruption error, stop and return that.+ if (noteMaybe.error().isDataCorruptionError()) {+ return STOP;+ }+ return CONTINUE;+ });++ return noteMaybe; } } // namespace symbolizer
@@ -18,9 +18,7 @@ #include <fcntl.h> #include <sys/stat.h>-#include <sys/types.h> #include <cstring>-#include <string> #include <glog/logging.h> @@ -96,7 +94,11 @@ } uint64_t mtime_ns = st.st_mtim.tv_sec * 1000'000'000LL + st.st_mtim.tv_nsec;- fileId_ = ElfFileId{st.st_dev, st.st_ino, st.st_size, mtime_ns};+ fileId_ = ElfFileId{+ to_narrow(st.st_dev),+ to_narrow(st.st_ino),+ to_narrow(st.st_size),+ mtime_ns}; length_ = st.st_size; int prot = PROT_READ;@@ -222,12 +224,12 @@ return {kInvalidElfFile, "not an ELF file (too short)"}; } - std::array<char, 5> elfMagBuf = {{0, 0, 0, 0, 0}};+ std::array<char, SELFMAG> elfMagBuf = {{0, 0, 0, 0}}; if (::lseek(fd_, 0, SEEK_SET) != 0 ||- fileops::read(fd_, elfMagBuf.data(), 4) != 4) {+ fileops::read(fd_, elfMagBuf.data(), SELFMAG) != SELFMAG) { return {kInvalidElfFile, "unable to read ELF file for magic number"}; }- if (std::strncmp(elfMagBuf.data(), ELFMAG, sizeof(ELFMAG)) != 0) {+ if (std::strncmp(elfMagBuf.data(), ELFMAG, SELFMAG) != 0) { return {kInvalidElfFile, "invalid ELF magic"}; } char c;@@ -279,7 +281,7 @@ // We support executable and shared object files and extracting debug info // from relocatable objects (.dwo sections in .o/.dwo files). The e_phnum and- // e_phentsize header fileds are not required for relocatable files.+ // e_phentsize header fields are not required for relocatable files. // https://docs.oracle.com/cd/E19620-01/805-4693/6j4emccrq/index.html if (elfHeader.e_type != ET_REL) { if (elfHeader.e_phnum == 0) {@@ -327,6 +329,11 @@ return folly::StringPiece(file_ + section.sh_offset, section.sh_size); } +folly::StringPiece ElfFile::getSegmentBody(+ const ElfPhdr& segment) const noexcept {+ return folly::StringPiece(file_ + segment.p_offset, segment.p_filesz);+}+ void ElfFile::validateStringTable(const ElfShdr& stringTable) const noexcept { FOLLY_SAFE_CHECK( stringTable.sh_type == SHT_STRTAB, "invalid type for string table");@@ -480,23 +487,47 @@ return getString(*linkSection, symbol.second->st_name); } -std::pair<const int, char const*> ElfFile::posixFadvise(- off_t offset, off_t len, int const advice) const noexcept {- if (fd_ == -1) {- return {1, "file not open"};- }- int res = posix_fadvise(fd_, offset, len, advice);- if (res != 0) {- return {res, "posix_fadvise failed for file"};- }- return {res, ""};+folly::Expected<span<const uint8_t>, ElfFile::FindNoteError>+ElfFile::getNoteGnuBuildId() const noexcept {+ auto filter = [](const Note& note) {+ return note.getName() == "GNU" && note.getType() == NT_GNU_BUILD_ID;+ };+ auto desc = [](auto note) { return note.getDesc(); };+ auto section = getSectionByName(".note.gnu.build-id");+ return iterateNotesInSections(section, filter).then(desc); } -std::pair<const int, char const*> ElfFile::posixFadvise(- int const advice) const noexcept {- return posixFadvise(0, 0, advice);+folly::Expected<ElfFile::Note, ElfFile::FindNoteError> ElfFile::findNoteByName(+ std::string_view name) const noexcept {+ auto filter = [name](const Note& note) { return note.getName() == name; };++ // If we get a success, or a data corruption error, return it.+ // otherwise iterate segments.+ auto foundMaybe = iterateNotesInSections(nullptr, filter);+ if (foundMaybe || foundMaybe.error().isDataCorruptionError()) {+ return foundMaybe;+ }++ foundMaybe = iterateNotesInSegments(nullptr, filter);+ return foundMaybe; } +folly::Expected<ElfFile::Note, ElfFile::FindNoteError> ElfFile::findNoteByType(+ size_t type) const noexcept {+ auto filter = [type](const Note& note) {+ return note.header()->n_type == type;+ };++ // If we get a success, or a data corruption error, return it.+ // otherwise iterate segments.+ auto foundMaybe = iterateNotesInSections(nullptr, filter);+ if (foundMaybe || foundMaybe.error().isDataCorruptionError()) {+ return foundMaybe;+ }++ foundMaybe = iterateNotesInSegments(nullptr, filter);+ return foundMaybe;+} } // namespace symbolizer } // namespace folly
@@ -29,7 +29,9 @@ #include <folly/Conv.h> #include <folly/Likely.h> #include <folly/Range.h>+#include <folly/container/span.h> #include <folly/lang/SafeAssert.h>+#include <folly/lang/cstring_view.h> #include <folly/portability/Config.h> #if FOLLY_HAVE_ELF@@ -49,12 +51,14 @@ using ElfAddr = FOLLY_ELF_ELFW(Addr); using ElfEhdr = FOLLY_ELF_ELFW(Ehdr); using ElfOff = FOLLY_ELF_ELFW(Off);+using ElfWord = FOLLY_ELF_ELFW(Word); using ElfPhdr = FOLLY_ELF_ELFW(Phdr); using ElfShdr = FOLLY_ELF_ELFW(Shdr); using ElfSym = FOLLY_ELF_ELFW(Sym); using ElfRel = FOLLY_ELF_ELFW(Rel); using ElfRela = FOLLY_ELF_ELFW(Rela); using ElfDyn = FOLLY_ELF_ELFW(Dyn);+using ElfNhdr = FOLLY_ELF_ELFW(Nhdr); // ElfFileId is supposed to uniquely identify any instance of an ELF binary. // It does that by using the file's inode, dev ID, size and modification time@@ -158,6 +162,8 @@ /** Get the actual section body */ folly::StringPiece getSectionBody(const ElfShdr& section) const noexcept; + folly::StringPiece getSegmentBody(const ElfPhdr& segment) const noexcept;+ /** Retrieve a string from a string table section */ const char* getString( const ElfShdr& stringTable, size_t offset) const noexcept;@@ -170,7 +176,7 @@ */ template <class Fn> const char* iterateStrings(const ElfShdr& stringTable, Fn fn) const- noexcept(is_nothrow_invocable_v<Fn, const char*>);+ noexcept(is_nothrow_invocable_v<Fn&, const char*>); /** * Iterate over program headers as long as fn(section) returns false.@@ -179,7 +185,7 @@ */ template <class Fn> const ElfPhdr* iterateProgramHeaders(Fn fn) const- noexcept(is_nothrow_invocable_v<Fn, ElfPhdr const&>);+ noexcept(is_nothrow_invocable_v<Fn&, ElfPhdr const&>); /** * Iterate over all sections for as long as fn(section) returns false.@@ -188,7 +194,7 @@ */ template <class Fn> const ElfShdr* iterateSections(Fn fn) const- noexcept(is_nothrow_invocable_v<Fn, ElfShdr const&>);+ noexcept(is_nothrow_invocable_v<Fn&, ElfShdr const&>); /** * Iterate over all sections with a given type. Similar to@@ -196,7 +202,7 @@ */ template <class Fn> const ElfShdr* iterateSectionsWithType(uint32_t type, Fn fn) const- noexcept(is_nothrow_invocable_v<Fn, ElfShdr const&>);+ noexcept(is_nothrow_invocable_v<Fn&, ElfShdr const&>); /** * Iterate over all sections with a given types. Similar to@@ -205,7 +211,7 @@ template <class Fn> const ElfShdr* iterateSectionsWithTypes( std::initializer_list<uint32_t> types, Fn fn) const- noexcept(is_nothrow_invocable_v<Fn, ElfShdr const&>);+ noexcept(is_nothrow_invocable_v<Fn&, ElfShdr const&>); /** * Iterate over all symbols within a given section.@@ -215,16 +221,16 @@ */ template <class Fn> const ElfSym* iterateSymbols(const ElfShdr& section, Fn fn) const- noexcept(is_nothrow_invocable_v<Fn, ElfSym const&>);+ noexcept(is_nothrow_invocable_v<Fn&, ElfSym const&>); template <class Fn> const ElfSym* iterateSymbolsWithType( const ElfShdr& section, uint32_t type, Fn fn) const- noexcept(is_nothrow_invocable_v<Fn, ElfSym const&>);+ noexcept(is_nothrow_invocable_v<Fn&, ElfSym const&>); template <class Fn> const ElfSym* iterateSymbolsWithTypes( const ElfShdr& section, std::initializer_list<uint32_t> types,- Fn fn) const noexcept(is_nothrow_invocable_v<Fn, ElfSym const&>);+ Fn fn) const noexcept(is_nothrow_invocable_v<Fn&, ElfSym const&>); /** * Iterate over entries within a given section.@@ -244,7 +250,7 @@ * * Returns {nullptr, nullptr} if not found. */- typedef std::pair<const ElfShdr*, const ElfSym*> Symbol;+ using Symbol = std::pair<const ElfShdr*, const ElfSym*>; Symbol getDefinitionByAddress(uintptr_t address) const noexcept; /**@@ -360,21 +366,187 @@ const ElfFileId& getFileId() const { return fileId_; } + enum class FindNoteFailureCode {+ NoteNotFound = 1,+ NoteUndersized,+ NoteUnaligned,+ };++ /** Describes the reason searching for a note has failed, including an error+ * code and a flag denoting if that error code is due to corruption in the ELF+ * file itself.+ */+ struct FindNoteError {+ FindNoteFailureCode failureCode;++ static inline constexpr std::string_view GetNoteErrorString[] = {+ "Note not found",+ "Note body undersized (smaller than specified in the note header)",+ "Note underaligned (aligned less than the note header)",+ };++ static constexpr std::string_view getErrorMessage(+ FindNoteError& err) noexcept {+ auto index = to_underlying(err.failureCode);+ if (index < 1 || to_unsigned(index) > std::size(GetNoteErrorString)) {+ return std::string_view{};+ }++ return GetNoteErrorString[index - 1];+ }++ bool isDataCorruptionError() {+ return failureCode == FindNoteFailureCode::NoteUndersized ||+ failureCode == FindNoteFailureCode::NoteUnaligned;+ }++ explicit FindNoteError(FindNoteFailureCode code) : failureCode(code) {}+ };++ /** Structure containing a note header and it's body */+ struct Note {+ folly::span<const uint8_t> note;+ explicit Note(folly::span<const uint8_t> note_) : note(note_) {}++ const ElfNhdr* header() const {+ return reinterpret_cast<const ElfNhdr*>(note.data());+ }++ folly::span<const uint8_t> body() const {+ return note.subspan(sizeof(*header()));+ }++ ElfWord getType() const {+ if (!header()) {+ return {};+ }+ return header()->n_type;+ }++ cstring_view getName() const {+ if (!header()) {+ return {};+ }++ // Subtract 1 to remove the trailing null character.+ return cstring_view(+ reinterpret_cast<const char*>(body().data()), header()->n_namesz - 1);+ }++ folly::span<const uint8_t> getDesc() const {+ if (!header()) {+ return span<const uint8_t>{};+ }++ size_t paddedNameSize = folly::align_ceil(header()->n_namesz, 4);+ return body().subspan(paddedNameSize);+ }++ static size_t alignedBodySize(const ElfNhdr& nhdr) {+ return folly::align_ceil(nhdr.n_namesz, 4) ++ folly::align_ceil(nhdr.n_descsz, 4);+ }++ static size_t alignedSize(const ElfNhdr& nhdr) {+ return sizeof(ElfNhdr) + alignedBodySize(nhdr);+ }++ /**+ * Get the aligned size of this note. If the header is null, 0 will be+ * returned.+ */+ size_t alignedSize() const { return header() ? alignedSize(*header()) : 0; }++ /**+ * Parse a note from a given set of bytes. If the supplied bytes is less+ * than the note header, or the aligned size of the body an unexpected will+ * be returned.+ */+ static folly::Expected<Note, FindNoteError> parse(+ folly::span<const uint8_t> noteBody) {+ if (noteBody.size() < sizeof(ElfNhdr)) {+ return Unexpected(FindNoteError(FindNoteFailureCode::NoteUndersized));+ }++ const ElfNhdr* nhdr = reinterpret_cast<const ElfNhdr*>(noteBody.data());+ if (alignedSize(*nhdr) > noteBody.size()) {+ return Unexpected(FindNoteError(FindNoteFailureCode::NoteUndersized));+ }++ return Note(noteBody.subspan(0, alignedSize(*nhdr)));+ }+ };+ /**- * Announce an intention to access file data in a specific pattern in the- * future. https://man7.org/linux/man-pages/man2/posix_fadvise.2.html+ * Iterate over notes in a given section, or all sections if section is null.+ * In the case no note is found, a pair will be returned with a nullptr for+ * the note header and an empty span for the note body.+ *+ * Note that notes are not unique, and notes in sections may also+ * be present when iterating over segments. */- std::pair<const int, char const*> posixFadvise(- off_t offset, off_t len, int const advice) const noexcept;- std::pair<const int, char const*> posixFadvise(- int const advice) const noexcept;+ template <class Fn>+ folly::Expected<Note, FindNoteError> iterateNotesInSections(+ const ElfShdr* section, Fn fn) const+ noexcept(is_nothrow_invocable_v<Fn&, const Note&>); + /**+ * Iterate over notes in a given segment, or all segments if segment is null.+ * In the case no note is found, a pair will be returned with a nullptr for+ * the note header and an empty span for the note body.+ *+ * Note that notes are not unique, and notes in segments may also+ * be present when iterating over sections.+ */+ template <class Fn>+ folly::Expected<Note, FindNoteError> iterateNotesInSegments(+ const ElfPhdr* segment, Fn fn) const+ noexcept(is_nothrow_invocable_v<Fn&, const Note&>);++ /**+ * Retrieve the content of .note.gnu.build-id, if available.+ */+ folly::Expected<span<const uint8_t>, FindNoteError> getNoteGnuBuildId()+ const noexcept;++ /**+ * Find a note by name in either a section or segment. If multiple+ * notes share the same name, the first match will be returned. An error will+ * be returned if the note is not found, or if there is underlying file+ * corruption.+ *+ * Empty is a valid input, and will return when notes with an empty name are+ * found. This can happen in ELF Cores, or other notes when the namespace+ * unknown. Read more at https://man7.org/linux/man-pages/man5/elf.5.html+ *+ * Note that notes are not unique, and notes in sections may also be in+ * segments. For this reason this method does not differentiate between+ * segments and sections.+ */+ folly::Expected<Note, FindNoteError> findNoteByName(+ std::string_view name) const noexcept;++ /**+ * Find a note by type in either a section or segment. If multiple+ * notes share the same type, the first match will be returned. An error will+ * be returned if the note is not found, or if there is underlying file+ * corruption.+ *+ * Note that notes are not unique, and notes in sections may also be in+ * segments. For this reason this method does not differentiate between+ * segments and sections.+ */+ folly::Expected<Note, FindNoteError> findNoteByType(+ size_t type) const noexcept;+ private: OpenResult init() noexcept; void reset() noexcept; ElfFile(const ElfFile&) = delete; ElfFile& operator=(const ElfFile&) = delete; + enum { CONTINUE = 0, STOP = 1 };+ void validateStringTable(const ElfShdr& stringTable) const noexcept; template <class T>@@ -428,6 +600,14 @@ return (offset + sizeof(T) <= length_) ? &at<T>(offset) : nullptr; }++ // Helper to iterate notes in a section or segment.+ // Takes in a StringPiece of the body of the section or segment, and iterates+ // over the notes in that body.+ template <class Fn>+ folly::Expected<Note, FindNoteError> iterateNotesInBodyHelper(+ folly::StringPiece body, Fn& fn) const+ noexcept(is_nothrow_invocable_v<Fn&, const Note&>); static constexpr size_t kFilepathMaxLen = 512; char filepath_[kFilepathMaxLen] = {};
@@ -27,7 +27,7 @@ #include <folly/Optional.h> #include <folly/Range.h>-#include <folly/experimental/symbolizer/Elf.h>+#include <folly/debugging/symbolizer/Elf.h> #include <folly/hash/Hash.h> #include <folly/memory/ReentrantAllocator.h> #include <folly/portability/Config.h>@@ -40,7 +40,12 @@ class ElfCacheBase { public: virtual std::shared_ptr<ElfFile> getFile(StringPiece path) = 0;- virtual ~ElfCacheBase() {}+ virtual ~ElfCacheBase() = default;+ ElfCacheBase() = default;+ ElfCacheBase(const ElfCacheBase&) = delete;+ ElfCacheBase& operator=(const ElfCacheBase&) = delete;+ ElfCacheBase(ElfCacheBase&&) = delete;+ ElfCacheBase& operator=(ElfCacheBase&&) = delete; }; /**@@ -69,9 +74,12 @@ char const* data, std::size_t size, reentrant_allocator<char> const& alloc) noexcept;+ ~Path() = default; Path() = delete; Path(Path const&) = delete; void operator=(Path const&) = delete;+ Path(Path&&) = delete;+ Path& operator=(Path&&) = delete; /* implicit */ operator StringPiece() const noexcept { return data_; } @@ -93,8 +101,11 @@ explicit Entry(StringPiece p, reentrant_allocator<char> alloc) noexcept : path{p.data(), p.size(), alloc}, file{std::allocate_shared<ElfFile>(alloc)} {}+ ~Entry() = default; Entry(Entry const&) = delete; Entry& operator=(Entry const& that) = delete;+ Entry(Entry&&) = delete;+ Entry& operator=(Entry&&) = delete; friend bool operator<(Entry const& a, Entry const& b) noexcept { return a.path < b.path;
@@ -21,7 +21,6 @@ #include <signal.h> #include <sys/types.h> -#include <algorithm> #include <atomic> #include <cerrno> #include <ctime>@@ -31,7 +30,7 @@ #include <glog/logging.h> #include <folly/ScopeGuard.h>-#include <folly/experimental/symbolizer/Symbolizer.h>+#include <folly/debugging/symbolizer/Symbolizer.h> #include <folly/lang/ToAscii.h> #include <folly/portability/SysSyscall.h> #include <folly/portability/Unistd.h>@@ -138,6 +137,41 @@ {0, nullptr, {}}, }; +template <typename...>+bool try_async_reraise(int signum, siginfo_t* info) {+ using folly::detail::linux_syscall;+#if defined(__linux__) && defined(SYS_pidfd_send_signal) && \+ defined(SYS_pidfd_open)+ constexpr long nr_pidfd_send_signal = SYS_pidfd_send_signal;+ constexpr long nr_pidfd_open = SYS_pidfd_open;+#else+ constexpr long nr_pidfd_send_signal = -1;+ constexpr long nr_pidfd_open = -1;+#endif+ if constexpr (kIsLinux && nr_pidfd_send_signal >= 0 && nr_pidfd_open >= 0) {+ constexpr auto kPIdfdSelf = -10000;+ // PIDFD_SELF handling introduced in linux-6.15 (released 2025-05-25)+ if (0 == linux_syscall(nr_pidfd_send_signal, kPIdfdSelf, signum, info, 0)) {+ return true;+ }+ // fallback using a real pidfd+ // TODO: remove fallback once minimum is linux-6.15+ if (errno != EBADF) { // EBADF here means PIDFD_SELF is not yet supported+ return false;+ }+ auto const tid = linux_syscall(FOLLY_SYS_gettid);+ // pidfd_open introduced in linux-5.3 (released 2019-09-15)+ int const fd = to_narrow(linux_syscall(nr_pidfd_open, tid, 0));+ if (-1 == fd) {+ return false;+ }+ // pidfd_send_signal introduced in linux-5.1 (released 2019-05-05)+ // no need to close(fd) after this - the process is about to terminate+ return 0 == linux_syscall(nr_pidfd_send_signal, fd, signum, info, 0);+ }+ return false;+}+ void signalHandler(int signum, siginfo_t* info, void* uctx); [[maybe_unused]] void callPreviousSignalHandler(int signum, siginfo_t* info) {@@ -145,35 +179,38 @@ // signal. The signal will remain blocked until the current call to the signal // handler returns. //- // For signals which arise from a faulting instruction, just restore the old- // disposition and return from the signal handler, returning control to the- // faulting instruction.- //- // Otherwise, restore the old disposition and explicitly re-raise the signal- // without returning to the faulting instruction.- //- // For these signals, we can approximately assume that when control returns to- // the faulting instruction, that instruction would fault again in the same- // way and generate the same signal again, triggering the default disposition- // for the signal. For example, an instruction which faulted, generating any- // of SIGSEGV, SIGILL, SIGFPE, or SIGBUS, can approximately be assumed to- // fault and generate the same signal again for the same reason.- //- // As a risk, it is possible that another thread or signal handler would- // resolve the fault concurrently with the current signal handling and before- // control returns to the faulting instruction, in which case the program- // continues without our signal handler registered for this signal anymore.+ // On Linux, use pidfd_send_signal to re-raise the original signal with the+ // original siginfo structure. Otherwise, just re-raise the original signal.+ // Re-raising the original signal with the original siginfo enables the Linux+ // kernel to record the true cause of the signal into the coredump. Otherwise,+ // the kernel would see the explicit call to raise() as the cause and would+ // record that in the coredump. //- // But, when it works, this technique preserves the true signal cause and- // makes that cause available to the debugger.- const bool fault = info->si_code > 0;+ // For a signal arising from a faulting instruction, there is an alternative+ // technique. After restoring disposition, the signal handler would simply+ // return. Then the faulting instruction would execute again and be expected+ // to trigger the same signal again. The second time, there would be no signal+ // handler to handle the signal, so the kernel would see this second execution+ // of the instruction as the true cause of the signal and would record that in+ // the coredump. However, this technique is subject to the race where another+ // thread might resolve the fault, causing the instruction's second execution+ // to resume without fault. This can be a problem since we do want the process+ // to terminate immediately with a coredump, and since the process resumes but+ // without the corresponding signal handler anymore so the next time the same+ // fault type happens there is no signal handler to report it. for (auto p = kFatalSignals; p->name; ++p) { if (p->number == signum) { sigaction(signum, &p->oldAction, nullptr);- if (!fault) {- raise(signum);+ if (try_async_reraise(signum, info)) {+ return; }- return;+ // Unblock the signal before raising it. Since our handler doesn't use+ // SA_NODEFER, the signal is currently blocked.+ sigset_t mask;+ sigemptyset(&mask);+ sigaddset(&mask, signum);+ pthread_sigmask(SIG_UNBLOCK, &mask, nullptr);+ raise(signum); } } @@ -182,6 +219,9 @@ memset(&sa, 0, sizeof(sa)); sa.sa_handler = SIG_DFL; sigaction(signum, &sa, nullptr);+ if (try_async_reraise(signum, info)) {+ return;+ } raise(signum); } @@ -516,7 +556,10 @@ // Small sigaltstack size threshold. // 51392 is known to cause the signal handler to stack overflow during // symbolization of trivial async stacks (e.g [] { CHECK(false); co_return; }).-constexpr size_t kSmallSigAltStackSize = 51392;+// 54016 is known to cause the signal handler to stack overflow during+// symbolization of less trivial async stacks. Setting 64KB to have a bit larger+// and "less" magical threshold.+constexpr size_t kSmallSigAltStackSize = 65536; [[maybe_unused]] bool isSmallSigAltStackEnabled() { stack_t ss;
@@ -47,7 +47,7 @@ * All these fatal callback must be added before calling * installFatalSignalCallbacks(), below. */-typedef void (*SignalCallback)();+using SignalCallback = void (*)(); void addFatalSignalCallback(SignalCallback cb); /**
@@ -18,11 +18,10 @@ #include <folly/tracing/AsyncStack.h> #include <memory>+#include <optional> -#include <folly/CppAttributes.h> #include <folly/Portability.h> #include <folly/portability/Config.h>-#include <folly/tracing/AsyncStack.h> #if FOLLY_HAVE_LIBUNWIND // Must be first to ensure that UNW_LOCAL_ONLY is defined@@ -31,7 +30,7 @@ #endif #if FOLLY_HAVE_BACKTRACE-#include <execinfo.h>+#include <execinfo.h> // @donotremove #endif namespace folly {@@ -87,7 +86,36 @@ #if FOLLY_HAVE_LIBUNWIND -inline bool getFrameInfo(unw_cursor_t* cursor, uintptr_t& ip) {+struct FrameInfo {+ /// The instruction pointer (ip) of the frame.+ uintptr_t uip;+ bool isSignalFrame;++ inline uintptr_t getAdjustedInstructionPointer(+ std::optional<FrameInfo> frameInfoPrev) const {+ bool adjustment;++ if constexpr (folly::kIsArchAArch64) {+ // This ip adjustment logic matches `unw_backtrace`.+ // https://github.com/libunwind/libunwind/blob/v1.8.2/src/aarch64/Gtrace.c#L554-L555+ // The ip is adjusted if there wasn't a previous frame,+ // or if the previous frame was NOT a signal frame.+ adjustment = !frameInfoPrev || !frameInfoPrev->isSignalFrame;+ } else {+ // Use previous instruction in normal (call) frames (because the+ // return address might not be in the same function for noreturn+ // functions) but not in signal frames.+ adjustment = !this->isSignalFrame;+ }++ return this->uip - adjustment;+ }+};++/// Gets the `FrameInfo` from libunwind and stores it in the out ref parameter.+///+/// @return true on success and false on error.+inline bool getFrameInfo(unw_cursor_t* cursor, FrameInfo& frameInfo) { unw_word_t uip; if (unw_get_reg(cursor, UNW_REG_IP, &uip) < 0) { return false;@@ -96,10 +124,10 @@ if (r < 0) { return false; }- // Use previous instruction in normal (call) frames (because the- // return address might not be in the same function for noreturn functions)- // but not in signal frames.- ip = uip - (r == 0);+ bool isSignalFrame = r > 0;++ frameInfo = FrameInfo{uip, isSignalFrame};+ return true; } @@ -122,9 +150,13 @@ if (unw_init_local(&cursor, &context) < 0) { return -1; }- if (!getFrameInfo(&cursor, *addresses)) {+ FrameInfo frameInfo;+ if (!getFrameInfo(&cursor, frameInfo)) { return -1; }++ std::optional<FrameInfo> frameInfoPrev; // no previous frame, yet.+ *addresses = frameInfo.getAdjustedInstructionPointer(frameInfoPrev); ++addresses; size_t count = 1; for (; count != maxAddresses; ++count, ++addresses) {@@ -135,9 +167,11 @@ if (r == 0) { break; }- if (!getFrameInfo(&cursor, *addresses)) {+ frameInfoPrev = frameInfo;+ if (!getFrameInfo(&cursor, frameInfo)) { return -1; }+ *addresses = frameInfo.getAdjustedInstructionPointer(frameInfoPrev); } return count; }
@@ -20,7 +20,7 @@ #include <folly/FBString.h> #include <folly/Range.h>-#include <folly/experimental/symbolizer/SymbolizedFrame.h>+#include <folly/debugging/symbolizer/SymbolizedFrame.h> namespace folly { class IOBuf;
@@ -16,22 +16,17 @@ #include <folly/debugging/symbolizer/Symbolizer.h> -#include <climits>-#include <cstdio> #include <cstdlib>-#include <iostream> #include <folly/FileUtil.h> #include <folly/Memory.h> #include <folly/ScopeGuard.h>-#include <folly/String.h> #include <folly/Synchronized.h> #include <folly/container/EvictingCacheMap.h>+#include <folly/debugging/symbolizer/Dwarf.h>+#include <folly/debugging/symbolizer/Elf.h>+#include <folly/debugging/symbolizer/ElfCache.h> #include <folly/debugging/symbolizer/detail/Debug.h>-#include <folly/experimental/symbolizer/Dwarf.h>-#include <folly/experimental/symbolizer/Elf.h>-#include <folly/experimental/symbolizer/ElfCache.h>-#include <folly/experimental/symbolizer/LineReader.h> #include <folly/lang/SafeAssert.h> #include <folly/lang/ToAscii.h> #include <folly/memory/SanitizeAddress.h>@@ -183,13 +178,12 @@ frame.found = true; frame.addr = address; frame.file = file;- frame.name = file->getSymbolName(file->getDefinitionByAddress(address)); #ifdef __roar__- if (!frame.name &&- setROARSymbolizedFrame(frame, address, mode, extraInlineFrames)) {+ if (setROARSymbolizedFrame(frame, address, mode, extraInlineFrames)) { return; } #endif+ frame.name = file->getSymbolName(file->getDefinitionByAddress(address)); Dwarf(elfCache, file.get()) .findAddress(address, mode, frame, extraInlineFrames);@@ -203,6 +197,25 @@ using UnsyncSymbolCache = EvictingCacheMap<uintptr_t, CachedSymbolizedFrames>; +/**+ * @param instructionAddr The address of an instruction after it has been+ * adjusted by the linker's `l_addr`.+ * @return true if the given address is contained in an executable segment of+ * `elfFile`.+ */+bool containedInExecutableSegment(+ const ElfFile& elfFile, ElfAddr instructionAddr) {+ return elfFile.iterateProgramHeaders([&](const ElfPhdr& sh) {+ bool executable = sh.p_flags & PF_X;+ bool loadable = sh.p_type == PT_LOAD;+ if (!(executable && loadable)) {+ return false;+ }+ return sh.p_vaddr <= instructionAddr &&+ instructionAddr < (sh.p_vaddr + sh.p_memsz);+ });+}+ } // namespace struct Symbolizer::SymbolCache : public Synchronized<UnsyncSymbolCache> {@@ -334,9 +347,11 @@ // Get the unrelocated, ELF-relative address by normalizing via the // address at which the object is loaded.- auto const adjusted = addr - reinterpret_cast<uintptr_t>(lmap->l_addr);+ auto const eaddr = static_cast<ElfAddr>(addr);+ auto const maddr = lmap->l_addr;+ auto const adjusted = eaddr < maddr ? ~ElfAddr(0) : eaddr - maddr; size_t numInlined = 0;- if (elfFile->getSectionContainingAddress(adjusted)) {+ if (containedInExecutableSegment(*elfFile, adjusted)) { if (mode_ == LocationInfoMode::FULL_WITH_INLINE && frameCount > addrCount) { size_t maxInline = std::min<size_t>(@@ -528,31 +543,54 @@ constexpr size_t kMaxStackTraceDepth = 100; template <size_t N, typename StackTraceFunc>-std::string getStackTraceStrImpl(StackTraceFunc func) {+std::string getStackTraceStrImpl(+ StackTraceFunc func, bool showFullInfo = false, size_t skip = 0) { FrameArray<N> addresses; if (!func(addresses)) { return ""; } else {- ElfCache elfCache;- Symbolizer symbolizer(&elfCache);- symbolizer.symbolize(addresses);-- StringSymbolizePrinter printer;- printer.println(addresses);- return printer.str();+ return detail::getStackTraceStr(+ addresses.addresses,+ addresses.frames,+ addresses.frameCount,+ showFullInfo,+ skip); } } } // namespace -std::string getStackTraceStr() {+namespace detail {+std::string getStackTraceStr(+ const uintptr_t* addresses,+ SymbolizedFrame* frames,+ size_t frameCount,+ bool showFullInfo,+ size_t skip) {+ if (frameCount == 0 || skip >= frameCount) {+ return {};+ }++ ElfCache elfCache;+ LocationInfoMode mode =+ showFullInfo ? LocationInfoMode::FULL : LocationInfoMode::FAST;+ Symbolizer symbolizer(&elfCache, mode);+ symbolizer.symbolize(addresses, frames, frameCount);++ StringSymbolizePrinter printer;+ printer.println(frames + skip, frameCount - skip);+ return printer.str();+}+} // namespace detail++std::string getStackTraceStr(bool showFullInfo, size_t skip) { return getStackTraceStrImpl<kMaxStackTraceDepth>(- getStackTrace<kMaxStackTraceDepth>);+ getStackTrace<kMaxStackTraceDepth>, showFullInfo, skip); } -std::string getAsyncStackTraceStr() {+std::string getAsyncStackTraceStr(size_t skip) { return getStackTraceStrImpl<kMaxStackTraceDepth>(- getAsyncStackTraceSafe<kMaxStackTraceDepth>);+ getAsyncStackTraceSafe<kMaxStackTraceDepth>, skip); } std::vector<std::string> getSuspendedStackTraces() {
@@ -25,11 +25,11 @@ #include <folly/Optional.h> #include <folly/Range.h> #include <folly/String.h>-#include <folly/experimental/symbolizer/Dwarf.h>-#include <folly/experimental/symbolizer/ElfCache.h>-#include <folly/experimental/symbolizer/StackTrace.h>-#include <folly/experimental/symbolizer/SymbolizePrinter.h>-#include <folly/experimental/symbolizer/SymbolizedFrame.h>+#include <folly/debugging/symbolizer/Dwarf.h>+#include <folly/debugging/symbolizer/ElfCache.h>+#include <folly/debugging/symbolizer/StackTrace.h>+#include <folly/debugging/symbolizer/SymbolizePrinter.h>+#include <folly/debugging/symbolizer/SymbolizedFrame.h> #include <folly/io/IOBuf.h> #include <folly/portability/Config.h> #include <folly/portability/Unistd.h>@@ -56,6 +56,13 @@ return false; } }++std::string getStackTraceStr(+ const uintptr_t* addresses,+ SymbolizedFrame* frames,+ size_t frameCount,+ bool showFullInfo,+ size_t skip = 0); } // namespace detail // Always inline these functions; they don't do much, and unittests rely@@ -269,22 +276,27 @@ #if FOLLY_HAVE_ELF && FOLLY_HAVE_DWARF /**- * Gets the stack trace for the current thread and returns a string- * representation. Convenience function meant for debugging and logging.- * Empty string indicates stack trace functionality is not available.+ * Gets the stack trace for the current thread, skipping the top `skip` frames,+ * and returns a string representation. Convenience function meant for+ * debugging and logging. Empty string indicates stack trace functionality+ * is not available. * * NOT async-signal-safe.+ * @param showFullInfo If true, include file names and line numbers (when+ * available).+ * @param skip Skip the top `skip` frames. */-std::string getStackTraceStr();+std::string getStackTraceStr(bool showFullInfo = false, size_t skip = 0); /**- * Gets the async stack trace for the current thread and returns a string- * representation. Convenience function meant for debugging and logging.- * Empty string indicates stack trace functionality is not available.+ * Gets the async stack trace for the current thread, skipping the top `skip`+ * frames, and returns a string representation. Convenience function meant for+ * debugging and logging. Empty string indicates stack trace functionality+ * is not available. * * NOT async-signal-safe. */-std::string getAsyncStackTraceStr();+std::string getAsyncStackTraceStr(size_t skip = 0); /** * Get the async stack traces (string representation) for suspended@@ -299,18 +311,45 @@ // Define these in the header, as headers are always available, but not all // platforms can link against the symbolizer library cpp sources. -inline std::string getStackTraceStr() {+inline std::string getStackTraceStr(+ bool /*showFullInfo*/ = false, size_t /*skip*/ = 0) { return ""; } -inline std::string getAsyncStackTraceStr() {+inline std::string getAsyncStackTraceStr(size_t /*skip*/ = 0) { return ""; } inline std::vector<std::string> getSuspendedStackTraces() { return {}; }++namespace detail {+inline std::string getStackTraceStr(+ const uintptr_t* /*addresses*/,+ SymbolizedFrame* /*frames*/,+ size_t /*frameCount*/,+ bool /*showFullInfo*/,+ size_t /*skip*/) {+ return "";+}+} // namespace detail #endif // FOLLY_HAVE_ELF && FOLLY_HAVE_DWARF++/**+ * Convert FrameArray to string representation.+ * Convenience function for debugging and logging.+ */+template <size_t N>+FOLLY_ALWAYS_INLINE std::string getStackTraceStr(+ FrameArray<N>& fa, bool showFullInfo = false, size_t skip = 0);++template <size_t N>+inline std::string getStackTraceStr(+ FrameArray<N>& fa, bool showFullInfo, size_t skip) {+ return detail::getStackTraceStr(+ fa.addresses, fa.frames, fa.frameCount, showFullInfo, skip);+} #if FOLLY_HAVE_SWAPCONTEXT
@@ -22,10 +22,6 @@ #include <dlfcn.h> #endif -#if FOLLY_HAVE_ELF-#include <link.h>-#endif- #if defined(__APPLE__) && !TARGET_OS_OSX #define FOLLY_DETAIL_HAS_R_DEBUG 0 #elif !defined(__linux__) || !FOLLY_HAVE_ELF || !FOLLY_HAVE_DWARF
@@ -16,8 +16,6 @@ #include <folly/detail/AsyncTrace.h> -#include <folly/Portability.h>- namespace folly { namespace async_tracing { FOLLY_ATTR_WEAK void logSetGlobalCPUExecutor(Executor*) noexcept {}
@@ -16,65 +16,37 @@ #pragma once -#include <atomic>-#include <cassert> #include <cstdint>-#include <system_error>+#include <cstdlib> -#include <folly/Exception.h>-#include <folly/portability/SysMman.h>-#include <folly/portability/Unistd.h>+#include <folly/lang/Exception.h>+#include <folly/memory/Malloc.h>+ namespace folly { namespace detail { -class MMapAlloc {- private:- size_t computeSize(size_t size) {- long pagesize = sysconf(_SC_PAGESIZE);- size_t mmapLength = ((size - 1) & ~(pagesize - 1)) + pagesize;- assert(size <= mmapLength && mmapLength < size + pagesize);- assert((mmapLength % pagesize) == 0);- return mmapLength;- }-+class MallocAlloc { public: void* allocate(size_t size) {- auto len = computeSize(size);-- int extraflags = 0;-#if defined(MAP_POPULATE)- extraflags |= MAP_POPULATE;-#endif- // MAP_HUGETLB is a perf win, but requires cooperation from the- // deployment environment (and a change to computeSize()).- void* mem = static_cast<void*>(mmap(- nullptr,- len,- PROT_READ | PROT_WRITE,- MAP_PRIVATE | MAP_ANONYMOUS | extraflags,- -1,- 0));- if (mem == reinterpret_cast<void*>(-1)) {- throw std::system_error(errno, errorCategoryForErrnoDomain());+ void* p = std::malloc(size);+ if (p == nullptr) {+ throw_exception<std::bad_alloc>(); }-#if !defined(MAP_POPULATE) && defined(MADV_WILLNEED)- madvise(mem, size, MADV_WILLNEED);-#endif-- return mem;+ return p; } - void deallocate(void* p, size_t size) {- auto len = computeSize(size);- munmap(p, len);- }+ void deallocate(void* p, size_t size) { sizedFree(p, size); } }; template <typename Allocator> struct GivesZeroFilledMemory : public std::false_type {}; -template <>-struct GivesZeroFilledMemory<MMapAlloc> : public std::true_type {};+/*+ * Example of how to specialize GivesZeroFilledMemory for custom allocator.+ *+ * template <>+ * struct GivesZeroFilledMemory<Alloc> : public std::true_type {};+ */ } // namespace detail } // namespace folly
@@ -18,6 +18,7 @@ #include <cerrno> #include <cstddef>+#include <functional> #include <string> #include <type_traits> @@ -34,9 +35,9 @@ // against interrupt and partial op completions. // Wrap call to f(args) in loop to retry on EINTR-template <class F, class... Args>-ssize_t wrapNoInt(F f, Args... args) {- ssize_t r;+template <class F, class... Args, class R = std::invoke_result_t<F&, Args&...>>+R wrapNoInt(F f, Args... args) {+ R r; do { r = f(args...); } while (r == -1 && errno == EINTR);
@@ -48,11 +48,11 @@ template <typename T> class GroupVarintBase { protected:- typedef GroupVarintTraits<T> Traits;+ using Traits = GroupVarintTraits<T>; enum : uint32_t { kHeaderSize = Traits::kHeaderSize }; public:- typedef T type;+ using type = T; /** * Number of integers encoded / decoded in one pass.@@ -93,7 +93,7 @@ } private:- typedef GroupVarint<T> Derived;+ using Derived = GroupVarint<T>; enum { kFullGroupSize = kHeaderSize + kGroupSize * sizeof(type) }; };
@@ -28,10 +28,11 @@ } [[noreturn]] void getNthMSBitImplThrow(size_t bitCount, sa_family_t family) {- throw std::invalid_argument(fmt::format(- FOLLY_FMT_COMPILE("Bit index must be < {} for addresses of type: {}"),- bitCount,- familyNameStr(family)));+ throw std::invalid_argument(+ fmt::format(+ FOLLY_FMT_COMPILE("Bit index must be < {} for addresses of type: {}"),+ bitCount,+ familyNameStr(family))); } } // namespace detail } // namespace folly
@@ -75,10 +75,11 @@ 0xff // /8 }}; if (oneMask > kBitCount || twoMask > kBitCount) {- throw std::invalid_argument(fmt::format(- "Invalid mask length: {}. Mask length must be <= {}",- std::max(oneMask, twoMask),- kBitCount));+ throw std::invalid_argument(+ fmt::format(+ "Invalid mask length: {}. Mask length must be <= {}",+ std::max(oneMask, twoMask),+ kBitCount)); } auto mask = std::min(oneMask, twoMask);
@@ -26,7 +26,7 @@ template <class T, size_t Amp> class MPMCPipelineStage { public:- typedef T value_type;+ using value_type = T; static constexpr size_t kAmplification = Amp; }; @@ -39,13 +39,13 @@ template <class T> struct PipelineStageInfo { static constexpr size_t kAmplification = 1;- typedef T value_type;+ using value_type = T; }; template <class T, size_t Amp> struct PipelineStageInfo<MPMCPipelineStage<T, Amp>> { static constexpr size_t kAmplification = Amp;- typedef T value_type;+ using value_type = T; }; /**@@ -54,7 +54,7 @@ template <class T> class MPMCPipelineStageImpl { public:- typedef T value_type;+ using value_type = T; template <class U, class... Stages> friend class MPMCPipeline;
@@ -17,9 +17,7 @@ #include <folly/detail/MemoryIdler.h> #include <climits>-#include <cstdio> #include <cstring>-#include <utility> #include <folly/GLog.h> #include <folly/Portability.h>@@ -55,7 +53,7 @@ // stack on demand. Before the main thread stack grows to its full extent, the // vma corresponding to the main thread stack is not yet fully allocated. It's // possible for the kernel to allocate the not-yet-allocated main thread stack- // vma to ramdon sbrk() or mmap() requests, and for the resulting regions from+ // vma to random sbrk() or mmap() requests, and for the resulting regions from // these requests to be used by other user code. If this happens, the madvise- // dontneed here is dangerous - it can zero arbitrary heap buffers! So it must // be skipped. In the case where this runs a fork() child in that thread which
@@ -79,9 +79,10 @@ // hash the pthread_t and the time to get the adjustment // Standard hash func isn't very good, so bit mix the result- uint64_t h = folly::hash::twang_mix64(folly::hash::hash_combine(- getCurrentThreadID(),- std::chrono::system_clock::now().time_since_epoch().count()));+ uint64_t h = folly::hash::twang_mix64(+ folly::hash::hash_combine(+ getCurrentThreadID(),+ std::chrono::system_clock::now().time_since_epoch().count())); // multiplying the duration by a floating point doesn't work, grr auto extraFrac = timeoutVariationFrac /
@@ -24,9 +24,12 @@ #include <folly/testing/TestUtil.h> #endif +#include <filesystem> #include <stdexcept> #include <thread> +#include <boost/regex.hpp>+ namespace folly { namespace detail { @@ -41,10 +44,10 @@ std::vector<std::string> res{std::string(kPerfBinaryPath)}; res.insert(res.end(), passed.begin(), passed.end()); - res.push_back("-p");+ res.emplace_back("-p"); res.push_back(folly::to<std::string>(get_cached_pid())); if (output) {- res.push_back("--output");+ res.emplace_back("--output"); res.push_back(output->path().string()); } return res;@@ -72,6 +75,8 @@ PerfScopedImpl& operator=(PerfScopedImpl&&) = delete; ~PerfScopedImpl() noexcept {+ waitUntilAttached();+ proc_.sendSignal(SIGINT); proc_.wait(); @@ -81,6 +86,22 @@ } private:+ void waitUntilAttached() {+ const boost::regex regex{R"(anon_inode:\[perf_event(:\w+)?\])"};+ const auto slashproc = std::filesystem::path("/proc");+ const auto fddir = slashproc / folly::to<std::string>(proc_.pid()) / "fd";+ while (true) {+ for (const auto& entry : std::filesystem::directory_iterator(fddir)) {+ std::error_code ec;+ const auto target = std::filesystem::read_symlink(entry.path(), ec);+ if (boost::regex_match(target.string(), regex)) {+ return;+ }+ }+ std::this_thread::yield();+ }+ }+ test::TemporaryFile outputFile_; Subprocess proc_; std::string* output_;
@@ -619,8 +619,9 @@ } inline void* noopExec(Op op, Data*, void*) {- if (op == Op::eAddr)+ if (op == Op::eAddr) { throw_exception<BadPolyAccess>();+ } return const_cast<void*>(static_cast<void const*>(&typeid(void))); } @@ -803,11 +804,12 @@ template <class Fun> // Fun = R(As...) const struct Sig { template <class T>- constexpr Fun T::*operator()(Fun T::*t) const /* nolint */ volatile noexcept {+ constexpr Fun T::* operator()(Fun T::* t) const+ /* nolint */ volatile noexcept { return t; } template <class F, class T>- constexpr F T::*operator()(F T::*t) const /* nolint */ volatile noexcept {+ constexpr F T::* operator()(F T::* t) const /* nolint */ volatile noexcept { return t; } };@@ -820,7 +822,7 @@ using Sig<R() const>::operator(); template <class T>- constexpr Fun T::*operator()(Fun T::*t) const noexcept {+ constexpr Fun T::* operator()(Fun T::* t) const noexcept { return t; } };@@ -831,7 +833,7 @@ using Sig<R(As...) const>::operator(); template <class T>- constexpr Fun T::*operator()(Fun T::*t) const noexcept {+ constexpr Fun T::* operator()(Fun T::* t) const noexcept { return t; } constexpr Fun* operator()(Fun* t) const noexcept { return t; }
@@ -1,84 +0,0 @@-/*- * Copyright (c) Meta Platforms, Inc. and affiliates.- *- * Licensed under the Apache License, Version 2.0 (the "License");- * you may not use this file except in compliance with the License.- * You may obtain a copy of the License at- *- * http://www.apache.org/licenses/LICENSE-2.0- *- * Unless required by applicable law or agreed to in writing, software- * distributed under the License is distributed on an "AS IS" BASIS,- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.- * See the License for the specific language governing permissions and- * limitations under the License.- */--#include <folly/detail/SplitStringSimd.h>-#include <folly/detail/SplitStringSimdImpl.h>--#include <folly/FBString.h>-#include <folly/FBVector.h>-#include <folly/small_vector.h>--namespace folly {-namespace detail {--template <typename Container>-void SimdSplitByCharImpl<Container>::keepEmpty(- char sep, folly::StringPiece what, Container& res) {- PlatformSimdSplitByChar<- simd::detail::SimdPlatform<std::uint8_t>,- /*ignoreEmpty*/ false>{}(sep, what, res);-}--template <typename Container>-void SimdSplitByCharImpl<Container>::dropEmpty(- char sep, folly::StringPiece what, Container& res) {- PlatformSimdSplitByChar<- simd::detail::SimdPlatform<std::uint8_t>,- /*ignoreEmpty*/ true>{}(sep, what, res);-}--template <typename Container>-void SimdSplitByCharImplToStrings<Container>::keepEmpty(- char sep, folly::StringPiece what, Container& res) {- PlatformSimdSplitByChar<- simd::detail::SimdPlatform<std::uint8_t>,- /*ignoreEmpty*/ false>{}(sep, what, res);-}--template <typename Container>-void SimdSplitByCharImplToStrings<Container>::dropEmpty(- char sep, folly::StringPiece what, Container& res) {- PlatformSimdSplitByChar<- simd::detail::SimdPlatform<std::uint8_t>,- /*ignoreEmpty*/ true>{}(sep, what, res);-}--// clang-format off-#define FOLLY_DETAIL_DEFINE_ALL_SIMD_SPLIT_OVERLOADS(...) \- template struct SimdSplitByCharImpl<std::vector<__VA_ARGS__>>; \- template struct SimdSplitByCharImpl<folly::fbvector<__VA_ARGS__, std::allocator<__VA_ARGS__>>>; \- template struct SimdSplitByCharImpl<folly::small_vector<__VA_ARGS__, 1, void>>; \- template struct SimdSplitByCharImpl<folly::small_vector<__VA_ARGS__, 2, void>>; \- template struct SimdSplitByCharImpl<folly::small_vector<__VA_ARGS__, 3, void>>; \- template struct SimdSplitByCharImpl<folly::small_vector<__VA_ARGS__, 4, void>>; \- template struct SimdSplitByCharImpl<folly::small_vector<__VA_ARGS__, 5, void>>; \- template struct SimdSplitByCharImpl<folly::small_vector<__VA_ARGS__, 6, void>>; \- template struct SimdSplitByCharImpl<folly::small_vector<__VA_ARGS__, 7, void>>; \- template struct SimdSplitByCharImpl<folly::small_vector<__VA_ARGS__, 8, void>>;-// clang-format on--FOLLY_DETAIL_DEFINE_ALL_SIMD_SPLIT_OVERLOADS(folly::StringPiece)--FOLLY_DETAIL_DEFINE_ALL_SIMD_SPLIT_OVERLOADS(std::string_view)--#undef FOLLY_DETAIL_DEFINE_ALL_SIMD_SPLIT_OVERLOADS--template struct SimdSplitByCharImplToStrings<std::vector<std::string>>;-template struct SimdSplitByCharImplToStrings<std::vector<fbstring>>;-template struct SimdSplitByCharImplToStrings<fbvector<std::string>>;-template struct SimdSplitByCharImplToStrings<fbvector<fbstring>>;-} // namespace detail-} // namespace folly
@@ -16,136 +16,26 @@ #pragma once -#include <string>-#include <vector> #include <folly/Range.h>+#include <folly/detail/SplitStringSimdImpl.h> namespace folly {--template <typename T, std::size_t M, typename P>-class small_vector;--template <typename T, typename Allocator>-class fbvector;--template <class Char>-class fbstring_core;--template <typename E, typename T, typename A, typename Storage>-class basic_fbstring;- namespace detail { -using PredeclareFbString = basic_fbstring<- char,- std::char_traits<char>,- std::allocator<char>,- fbstring_core<char>>;- template <typename Container>-struct SimdSplitByCharImpl {- static void keepEmpty(char sep, folly::StringPiece what, Container& res);- static void dropEmpty(char sep, folly::StringPiece what, Container& res);-};--// Different name to easier identify in the stack potential performance issues-template <typename Container>-struct SimdSplitByCharImplToStrings {- static void keepEmpty(char sep, folly::StringPiece what, Container& res);- static void dropEmpty(char sep, folly::StringPiece what, Container& res);-};--template <typename T>-constexpr bool isSimdSplitSupportedStringViewType =- std::is_same<T, folly::StringPiece>::value ||- std::is_same<T, std::string_view>::value;--template <typename T>-constexpr bool isSimdSplitSupportedStringType =- std::is_same<T, PredeclareFbString>::value ||- std::is_same<T, std::string>::value;--template <typename>-struct SimdSplitByCharIsDefinedFor {- static constexpr bool value = false;-};--template <typename T>-struct SimdSplitByCharIsDefinedFor<std::vector<T>> {- static constexpr bool value = isSimdSplitSupportedStringViewType<T> ||- isSimdSplitSupportedStringType<T>;-};--template <typename T, typename A>-struct SimdSplitByCharIsDefinedFor<folly::fbvector<T, A>>- : SimdSplitByCharIsDefinedFor<std::vector<T, A>> {};--template <typename T, std::size_t M>-struct SimdSplitByCharIsDefinedFor<folly::small_vector<T, M, void>> {- static constexpr bool value =- isSimdSplitSupportedStringViewType<T> && 0 < M && M <= 8;-};--template <typename Container>-std::enable_if_t<- isSimdSplitSupportedStringViewType<typename Container::value_type>>-simdSplitByChar(- char sep, folly::StringPiece what, Container& res, bool ignoreEmpty) {- static_assert(- SimdSplitByCharIsDefinedFor<Container>::value,- "simd split by char is supported only for vector/fbvector/small_vector, with small size <= 8."- " The resulting string type has to string_view or StringPiece."- " There is also a special case of (fb)vector<(fb)string> for legacy compatibility");- if (ignoreEmpty) {- SimdSplitByCharImpl<Container>::dropEmpty(sep, what, res);- } else {- SimdSplitByCharImpl<Container>::keepEmpty(sep, what, res);- }-}--template <typename Container>-std::enable_if_t<isSimdSplitSupportedStringType<typename Container::value_type>>-simdSplitByChar(+void simdSplitByChar( char sep, folly::StringPiece what, Container& res, bool ignoreEmpty) {- static_assert(- SimdSplitByCharIsDefinedFor<Container>::value,- "simd split by char is supported only for vector/fbvector/small_vector, with small size <= 8."- " The resulting string type has to string_view or StringPiece."- " There is also a special case of (fb)vector<(fb)string> for legacy compatibility");+ using Platform = simd::detail::SimdPlatform<std::uint8_t>; if (ignoreEmpty) {- SimdSplitByCharImplToStrings<Container>::dropEmpty(sep, what, res);+ PlatformSimdSplitByChar<+ Platform,+ /*ignoreEmpty*/ true>{}(sep, what, res); } else {- SimdSplitByCharImplToStrings<Container>::keepEmpty(sep, what, res);+ PlatformSimdSplitByChar<+ Platform,+ /*ignoreEmpty*/ false>{}(sep, what, res); } }--// clang-format off-#define FOLLY_DETAIL_DECLARE_ALL_SIMD_SPLIT_OVERLOADS(...) \- extern template struct SimdSplitByCharImpl<std::vector<__VA_ARGS__>>; \- extern template struct SimdSplitByCharImpl<folly::fbvector<__VA_ARGS__, std::allocator<__VA_ARGS__>>>; \- extern template struct SimdSplitByCharImpl<folly::small_vector<__VA_ARGS__, 1, void>>; \- extern template struct SimdSplitByCharImpl<folly::small_vector<__VA_ARGS__, 2, void>>; \- extern template struct SimdSplitByCharImpl<folly::small_vector<__VA_ARGS__, 3, void>>; \- extern template struct SimdSplitByCharImpl<folly::small_vector<__VA_ARGS__, 4, void>>; \- extern template struct SimdSplitByCharImpl<folly::small_vector<__VA_ARGS__, 5, void>>; \- extern template struct SimdSplitByCharImpl<folly::small_vector<__VA_ARGS__, 6, void>>; \- extern template struct SimdSplitByCharImpl<folly::small_vector<__VA_ARGS__, 7, void>>; \- extern template struct SimdSplitByCharImpl<folly::small_vector<__VA_ARGS__, 8, void>>;-// clang-format on--FOLLY_DETAIL_DECLARE_ALL_SIMD_SPLIT_OVERLOADS(folly::StringPiece)--FOLLY_DETAIL_DECLARE_ALL_SIMD_SPLIT_OVERLOADS(std::string_view)--extern template struct SimdSplitByCharImplToStrings<std::vector<std::string>>;-extern template struct SimdSplitByCharImplToStrings<- std::vector<PredeclareFbString>>;-extern template struct SimdSplitByCharImplToStrings<- fbvector<std::string, std::allocator<std::string>>>;-extern template struct SimdSplitByCharImplToStrings<- fbvector<PredeclareFbString, std::allocator<PredeclareFbString>>>;--#undef FOLLY_DETAIL_DECLARE_ALL_SIMD_SPLIT_OVERLOADS } // namespace detail } // namespace folly
@@ -32,17 +32,12 @@ #include <arm_neon.h> #endif -// This file is not supposed to be included by users.-// It should be included in CPP file which exposes apis.-// It is a header file to test different platforms.--// All funcitons are force inline because they are merged into big hiddend-// noinline functions namespace folly { namespace detail { template <bool ignoreEmpty, typename Container>-void splitByCharScalar(char sep, folly::StringPiece what, Container& res) {+FOLLY_NOINLINE void splitByCharScalar(+ char sep, folly::StringPiece what, Container& res) { const char* prev = what.data(); const char* f = prev; const char* l = what.data() + what.size();@@ -72,8 +67,8 @@ using reg_t = typename Platform::reg_t; template <typename Container>- FOLLY_ALWAYS_INLINE void emplaceBack(- Container& res, const std::uint8_t* f, const std::uint8_t* l) const {+ FOLLY_ALWAYS_INLINE static void emplaceBack(+ Container& res, const std::uint8_t* f, const std::uint8_t* l) { if (ignoreEmpty && f == l) { return; }@@ -81,12 +76,11 @@ } template <typename Uint, typename BitsPerElement, typename Container>- FOLLY_ALWAYS_INLINE void outputStringsFoMmask(+ FOLLY_ALWAYS_INLINE static void outputStringsFoMmask( std::pair<Uint, BitsPerElement> mmask, const std::uint8_t* pos, const std::uint8_t*& prev,- Container& res) const { // reserve was not beneficial on benchmarks.-+ Container& res) { Uint mmaskBits = mmask.first; while (mmaskBits) { auto counted = folly::findFirstSet(mmaskBits) - 1;@@ -103,7 +97,6 @@ template <typename Container> struct ForEachDelegate {- const PlatformSimdSplitByChar& self; std::uint8_t sep; const std::uint8_t*& prev; Container& res;@@ -114,14 +107,14 @@ reg_t loaded = Platform::loada(ptr, ignore); auto mmask = simd::movemask<std::uint8_t>(Platform::equal(loaded, sep), ignore);- self.outputStringsFoMmask(mmask, ptr, prev, res);+ outputStringsFoMmask(mmask, ptr, prev, res); return false; } }; template <typename Container>- FOLLY_ALWAYS_INLINE void operator()(- char sep, folly::StringPiece what, Container& res) const {+ FOLLY_NOINLINE static void simdSplitByChar(+ char sep, folly::StringPiece what, Container& res) { const std::uint8_t* what_f = reinterpret_cast<const std::uint8_t*>(what.data()); const std::uint8_t* what_l = what_f + what.size();@@ -129,10 +122,16 @@ const std::uint8_t* prev = what_f; ForEachDelegate<Container> delegate{- *this, static_cast<std::uint8_t>(sep), prev, res};+ static_cast<std::uint8_t>(sep), prev, res}; simd::detail::simdForEachAligning</*unrolling*/ 1>( Platform::kCardinal, what_f, what_l, delegate); emplaceBack(res, prev, what_l);+ }++ template <typename Container>+ FOLLY_ALWAYS_INLINE void operator()(+ char sep, folly::StringPiece what, Container& res) const {+ return simdSplitByChar(sep, what, res); } };
@@ -23,8 +23,10 @@ #include <folly/ConstexprMath.h> #include <folly/Utility.h> #include <folly/detail/thread_local_globals.h>+#include <folly/hash/MurmurHash.h> #include <folly/lang/Hint.h> #include <folly/memory/SanitizeLeak.h>+#include <folly/random/hash.h> #include <folly/synchronization/CallOnce.h> constexpr auto kSmallGrowthFactor = 1.1;@@ -33,13 +35,17 @@ namespace folly { namespace threadlocal_detail { -struct rand_engine {- using result_type = unsigned int;- result_type operator()() { return to_unsigned(std::rand()); }- static constexpr result_type min() { return 0; }- static constexpr result_type max() { return RAND_MAX; }+namespace {++struct murmur_hash_fn {+ std::uint64_t operator()(std::uint64_t const val) const noexcept {+ auto const ptr = reinterpret_cast<const char*>(&val);+ return folly::hash::murmurHash64(ptr, sizeof(val), 0u);+ } }; +} // namespace+ SharedPtrDeleter::SharedPtrDeleter(std::shared_ptr<void> const& ts) noexcept : ts_{ts} {} SharedPtrDeleter::SharedPtrDeleter(SharedPtrDeleter const& that) noexcept@@ -67,7 +73,10 @@ return false; } auto const size = threadElements.size();- rand_engine rng;+ // NOLINTNEXTLINE+ static std::atomic<std::uint64_t> rng_seed_{to_unsigned(std::rand())};+ using engine = hash_counter_engine<murmur_hash_fn, std::uint64_t>;+ thread_local engine rng{rng_seed_.fetch_add(1, std::memory_order_relaxed)}; std::uniform_int_distribution<size_t> dist{0, size - 1}; if (!(dist(rng) < constexpr_log2(size))) { return true;@@ -182,7 +191,8 @@ return true; } - auto rlock = meta->allId2ThreadEntrySets_[id].tryRLock();+ auto& set = meta->allId2ThreadEntrySets_[id];+ auto rlock = set.tryRLock(); if (!rlock) { // Try lock failed. Skip checking in this case. Avoids // getting stuck in case this validation is called when@@ -190,6 +200,14 @@ return true; } + // Check if there are pending inserts. If so, skip validation as the+ // ThreadEntrySet may not have been updated yet due to the optimization+ // in resetElementImplAfterSet that defers updates when the lock cannot+ // be acquired.+ if (set.hasPendingInserts()) {+ return true;+ }+ return elements[id].ptr == rlock->getPtrForThread(this); } @@ -260,8 +278,12 @@ static PthreadKeyUnregister instance_; }; -StaticMetaBase::StaticMetaBase(ThreadEntry* (*threadEntry)(), bool strict)- : nextId_(1), threadEntry_(threadEntry), strict_(strict) {+StaticMetaBase::StaticMetaBase(+ ThreadEntry* (*threadEntry)(), bool strict, bool allowsAccessAllThreads)+ : nextId_(1),+ threadEntry_(threadEntry),+ strict_(strict),+ allowsAccessAllThreads_(allowsAccessAllThreads) { int ret = pthread_key_create(&pthreadKey_, &onThreadExit); checkPosixError(ret, "pthread_key_create failed"); PthreadKeyUnregister::registerKey(pthreadKey_);@@ -730,7 +752,16 @@ * release the element @id. */ void* ThreadEntry::releaseElement(uint32_t id) {- auto rlocked = meta->allId2ThreadEntrySets_[id].rlock();+ auto& set = meta->allId2ThreadEntrySets_[id];++ // Check if there are pending inserts that need to be processed first.+ // This must be done before calling release() to ensure synchronization.+ if (set.hasPendingInserts()) {+ // Acquire write lock to drain pending inserts before we proceed+ auto wlocked = set.wlock();+ }++ auto rlocked = set.rlock(); auto capacity = getElementsCapacity(); void* ptrToReturn = (capacity >= id) ? elements[id].release() : nullptr; auto slot = rlocked->getIndexFor(this);@@ -759,7 +790,46 @@ void ThreadEntry::resetElementImplAfterSet( const ElementWrapper& element, uint32_t id) { auto& set = meta->allId2ThreadEntrySets_[id];- auto rlock = set.rlock();++ // Try to acquire read lock without blocking+ auto triedRlock = set.tryRLock();+ if (!triedRlock) {+ // Failed to acquire the lock. If the old value is null, we can defer+ // the ThreadEntrySet update.+ ElementWrapper oldWrapper = elements[id];+ // The pending inserts optimization is only useful when accessAllThreads()+ // is available and can cause contention. Void tags don't support+ // accessAllThreads(), so skip this optimization in that case.+ if (!removed_ && meta->allowsAccessAllThreads_ &&+ oldWrapper.ptr == nullptr) {+ elements[id] = element;+ // Defer cleanup and ThreadEntrySet update since we don't hold the lock+ ThreadEntrySet::PendingElement pendingElement;+ pendingElement.threadEntry = this;+ pendingElement.wrapper = element;+ pendingElement.oldWrapper = oldWrapper;+ pendingElement.elementId = id;+ set.deferInsert(pendingElement);+ return;+ }+ // Fall back to acquiring the lock since we need to ensure consistency+ }++ // Acquire a regular rlock. Reuse the try lock if we have it.+ auto rlock = triedRlock+ ? StaticMetaBase::SynchronizedThreadEntrySet::RLockedPtr(+ std::move(triedRlock))+ : set.rlock();++ // Drain pending inserts before cleanup. This is critical when resetting+ // to null (!null -> null) to ensure we drain any prior pending insert+ // for this thread before the caller deletes the element pointer.+ if (set.hasPendingInserts()) {+ rlock.unlock();+ auto wlock = set.wlock(); // Drains pending inserts+ rlock = wlock.moveFromWriteToRead();+ }+ cleanupElement(id); elements[id] = element; if (removed_) {@@ -769,6 +839,8 @@ return; } if (element.ptr != nullptr && !rlock->contains(this)) {+ // Contention can occur here when upgrading from rlock to wlock.+ // This is a common case during null -> non-null transitions. meta->ensureThreadEntryIsInSet(this, set, rlock); } auto slot = rlock->getIndexFor(this);
@@ -285,6 +285,16 @@ /* implicit */ Element(ThreadEntry* entry = nullptr) : threadEntry(entry) {} }; + // struct for deferred insert - only used in pendingInserts.+ struct PendingElement {+ ThreadEntry* threadEntry{};+ ElementDisposeInfo wrapper;+ // Old wrapper that needs cleanup when this element is inserted+ ElementDisposeInfo oldWrapper;+ // Element ID for this wrapper (used when deferring cleanup)+ uint32_t elementId{0};+ };+ // Vector of ThreadEntry for fast iteration during accessAllThreads. using ElementVector = std::vector<Element>; ElementVector threadElements;@@ -372,7 +382,8 @@ } }; - StaticMetaBase(ThreadEntry* (*threadEntry)(), bool strict);+ StaticMetaBase(+ ThreadEntry* (*threadEntry)(), bool strict, bool allowsAccessAllThreads); FOLLY_EXPORT static ThreadEntryList* getThreadEntryList(); @@ -402,8 +413,102 @@ ElementWrapper& getElement(EntryID* ent); - using SynchronizedThreadEntrySet = folly::Synchronized<ThreadEntrySet>;+ /**+ * Wrapper around Synchronized<ThreadEntrySet> that automatically drains+ * pending inserts whenever a write lock is acquired.+ */+ class SynchronizedThreadEntrySet {+ public:+ using Base = folly::Synchronized<ThreadEntrySet>;+ using RLockedPtr = typename Base::RLockedPtr;+ using WLockedPtr = typename Base::WLockedPtr; + SynchronizedThreadEntrySet() = default;++ // Forward rlock() to the underlying Synchronized+ auto rlock() { return synchronized_.rlock(); }+ auto rlock() const { return synchronized_.rlock(); }++ // Forward tryRLock() to the underlying Synchronized+ auto tryRLock() { return synchronized_.tryRLock(); }+ auto tryRLock() const { return synchronized_.tryRLock(); }++ // wlock() automatically completes pending inserts+ auto wlock() {+ auto lock = synchronized_.wlock();+ completePendingInserts(*lock);+ return lock;+ }++ bool hasPendingInserts() const { return pendingInsertsCount_.load() > 0; }++ /**+ * Add the given element to the pendingInserts_ queue to be drained later.+ */+ void deferInsert(const ThreadEntrySet::PendingElement& pendingElement) {+ auto lock = pendingInserts_.wlock();+ lock->push_back(pendingElement);+ incrementPendingInsertsCount();+ }++ private:+ /**+ * Increment the pending inserts count. Called when adding to the+ * pending inserts queue.+ */+ void incrementPendingInsertsCount() { pendingInsertsCount_.fetch_add(1); }+ /**+ * Complete pending inserts by draining the pending inserts list and+ * updating the ThreadEntrySet. This should be called while holding+ * the write lock on the ThreadEntrySet.+ */+ void completePendingInserts(ThreadEntrySet& set) {+ // Drain pending inserts by swapping out the vector+ std::vector<ThreadEntrySet::PendingElement> pending;+ pendingInserts_.wlock()->swap(pending);++ for (auto& pendingElement : pending) {+ ThreadEntrySet::Element element{pendingElement.threadEntry};+ element.wrapper = pendingElement.wrapper;+ auto iter = set.entryToVectorSlot.find(pendingElement.threadEntry);+ if (iter != set.entryToVectorSlot.end()) {+ // Entry already present. Update the wrapper.+ DCHECK_EQ(+ element.threadEntry,+ set.threadElements[iter->second].threadEntry);+ set.threadElements[iter->second].wrapper = pendingElement.wrapper;+ } else {+ // Insert new entry+ set.threadElements.push_back(element);+ auto idx = set.threadElements.size() - 1;+ set.entryToVectorSlot.insert(iter, {element.threadEntry, idx});+ }++ // The old wrapper should always be null+ DCHECK(pendingElement.oldWrapper.ptr == nullptr);+ }++ if (!pending.empty()) {+ [[maybe_unused]] auto oldCount =+ pendingInsertsCount_.fetch_sub(pending.size());+ DCHECK_GE(oldCount, pending.size());+ }+ }++ Base synchronized_;+ /**+ * Holds elements that failed to acquire read lock during resetElement().+ * This typically occurs due to contention with accessAllThreads(), which+ * may hold the write lock for extended periods.+ *+ * Pending elements are drained via completePendingInserts(), which is+ * invoked whenever the write lock is acquired on the ThreadEntrySet.+ */+ folly::Synchronized<std::vector<ThreadEntrySet::PendingElement>>+ pendingInserts_;+ folly::relaxed_atomic<size_t> pendingInsertsCount_{0};+ };+ /* * Helper inline methods to add/remove/clear ThreadEntry* from * allId2ThreadEntrySets_@@ -479,9 +584,7 @@ ThreadEntry* threadEntry, uint32_t idval, size_t& newCapacity); span<SynchronizedThreadEntrySet* const> getThreadEntrySetsPtrSpan() {- const auto sets = allId2ThreadEntrySets_.as_view().as_ptr_span();- const size_t nextId = nextId_.load();- return sets.subspan(0, std::min(sets.size(), nextId));+ return allId2ThreadEntrySets_.as_view().as_ptr_span(nextId_.load()); } relaxed_atomic_uint32_t nextId_;@@ -508,6 +611,7 @@ pthread_key_t pthreadKey_; ThreadEntry* (*threadEntry_)(); bool strict_;+ bool allowsAccessAllThreads_; // Total size of ElementWrapper arrays across all threads. This is meant // to surface the overhead of thread local tracking machinery since the array // can be sparse when there are lots of thread local variables under the same@@ -580,7 +684,8 @@ public: StaticMeta()- : StaticMetaBase(&StaticMeta::getThreadEntrySlow, IsAccessModeStrict) {+ : StaticMetaBase(+ &StaticMeta::getThreadEntrySlow, IsAccessModeStrict, !IsTagVoid) { AtFork::registerHandler( this, /*prepare*/ &StaticMeta::preFork,
@@ -98,4 +98,18 @@ } } +inline constexpr const char* base64PHPStrictRemoveWhitespaces(+ const char* f, const char* l, char* o) noexcept {+ return base64PHPStrictRemoveWhitespacesScalar(f, l, o);+}++inline constexpr Base64DecodeResult base64PHPStrictDecode(+ const char* f, const char* l, char* o) noexcept {+ const char* o_l = base64PHPStrictRemoveWhitespaces(f, l, o);++ // Encoded string is always longer so reuse output to avoid extra allocation+ Base64DecodeResult result = base64Decode(o, o_l, o);+ return {result.isSuccess, result.o};+}+ } // namespace folly::detail::base64_detail
@@ -18,6 +18,8 @@ #include <cstddef> #include <cstdint>+#include <folly/Utility.h>+#include <folly/detail/base64_detail/Base64Constants.h> namespace folly::detail::base64_detail { @@ -70,6 +72,14 @@ res += extra; return res;+}++constexpr std::size_t base64PHPStrictDecodeRequiredOutputSize(+ const char* f, const char* l) {+ std::size_t n = static_cast<std::size_t>(l - f);++ // Reserve full input size as output is used for temporary storage+ return n; } struct Base64DecodeResult {
@@ -62,6 +62,15 @@ return base64DecodeRule(x); } +constexpr std::uint8_t base64PHPStrictDecodeSkipRule(char x) {+ // This is different from std::isspace and std::isblank in <cctype>+ if (x == '\t' || x == '\n' || x == '\r' || x == ' ') {+ return 0;+ }++ return 1;+}+ template <typename DecodeChar> constexpr auto buildDecodeTable(DecodeChar decodeChar) { std::array<char, 256> res = {};@@ -71,9 +80,19 @@ return res; } +constexpr auto buildBase64PHPStrictDecodeSkipTable() {+ std::array<std::uint8_t, 256> res = {};+ for (std::size_t i = 0; i != res.size(); ++i) {+ res[i] = base64PHPStrictDecodeSkipRule(static_cast<char>(i));+ }+ return res;+}+ constexpr std::array<char, 256> kBase64DecodeTable = buildDecodeTable(base64DecodeRule); constexpr std::array<char, 256> kBase64URLDecodeTable = buildDecodeTable(base64URLDecodeRule);+constexpr std::array<std::uint8_t, 256> kBase64PHPStrictDecodeSkipTable =+ buildBase64PHPStrictDecodeSkipTable(); } // namespace folly::detail::base64_detail::constants
@@ -18,8 +18,11 @@ #include <array> #include <cstdint>+#include <folly/Utility.h> #include <folly/detail/base64_detail/Base64Common.h> #include <folly/detail/base64_detail/Base64Constants.h>+#include <folly/memory/UninitializedMemoryHacks.h>+#include <folly/portability/Constexpr.h> namespace folly::detail::base64_detail { @@ -29,9 +32,9 @@ constexpr std::array<std::uint8_t, 3> base64DecodePack4To3( std::uint8_t aaa, std::uint8_t bbb, std::uint8_t ccc, std::uint8_t ddd) {- std::uint8_t aaab = (aaa << 2) | (bbb >> 4);- std::uint8_t bbcc = (bbb << 4) | (ccc >> 2);- std::uint8_t cddd = (ccc << 6) | ddd;+ std::uint8_t aaab = static_cast<std::uint8_t>((aaa << 2) | (bbb >> 4));+ std::uint8_t bbcc = static_cast<std::uint8_t>((bbb << 4) | (ccc >> 2));+ std::uint8_t cddd = static_cast<std::uint8_t>((ccc << 6) | ddd); return {{aaab, bbcc, cddd}}; }@@ -140,7 +143,7 @@ std::uint8_t aaa = constants::kBase64DecodeTable[atAsU8(f, 0)]; std::uint8_t bbb = constants::kBase64DecodeTable[atAsU8(f, 1)]; - *o++ = (aaa << 2) | (bbb >> 4);+ *o++ = static_cast<std::uint8_t>((aaa << 2) | (bbb >> 4)); errorAccumulator |= aaa | bbb; if (f[2] == '=' && f[3] == '=') {@@ -210,7 +213,7 @@ std::uint8_t aaa = constants::kBase64URLDecodeTable[atAsU8(f, 0)]; std::uint8_t bbb = constants::kBase64URLDecodeTable[atAsU8(f, 1)]; - *o++ = (aaa << 2) | (bbb >> 4);+ *o++ = static_cast<std::uint8_t>((aaa << 2) | (bbb >> 4)); errorAccumulator |= aaa | bbb; // This will detect incorrect padding as well f += 2;@@ -257,6 +260,21 @@ errorAccumulator != static_cast<std::uint8_t>(constants::kDecodeErrorMarker), o};+}++constexpr inline const char* base64PHPStrictRemoveWhitespacesScalar(+ const char* f, const char* l, char* o) noexcept {+ constexpr auto& skips =+ detail::base64_detail::constants::kBase64PHPStrictDecodeSkipTable;+ std::size_t pos = 0;++ while (f != l) {+ auto v = *f++;+ o[pos] = v;+ pos += skips[to_unsigned(v)];+ }++ return o + pos; } } // namespace folly::detail::base64_detail
@@ -124,7 +124,7 @@ struct entry { using entry_type = T; [[FOLLY_ATTR_NO_UNIQUE_ADDRESS]] T entry_value;- constexpr auto operator<=>(const entry&) const = default;+ auto operator<=>(const entry&) const = default; }; template <typename Seq, typename...>@@ -133,14 +133,14 @@ template <size_t... Is, typename... Ts> struct tuple_base<std::index_sequence<Is...>, Ts...> : entry<Is, Ts>... { using tuple_base_list = tag_t<entry<Is, Ts>...>;- constexpr auto operator<=>(const tuple_base&) const = default;+ auto operator<=>(const tuple_base&) const = default; }; } // namespace detail template <typename... Ts> struct tuple : detail::tuple_base<std::index_sequence_for<Ts...>, Ts...> {- constexpr auto operator<=>(const tuple&) const = default;+ auto operator<=>(const tuple&) const = default; }; template <typename... Ts> tuple(Ts...) -> tuple<Ts...>;@@ -267,9 +267,9 @@ } // namespace folly::detail::lite_tuple namespace folly {-template <typename... As>-struct safe_alias_of<::folly::detail::lite_tuple::tuple<As...>>- : safe_alias_of_pack<As...> {};+template <typename... As, safe_alias Default>+struct safe_alias_of<::folly::detail::lite_tuple::tuple<As...>, Default>+ : safe_alias_of_pack<Default, As...> {}; } // namespace folly namespace std {
@@ -14,4 +14,4 @@ * limitations under the License. */ -#include <folly/json/dynamic-inl.h>+#include <folly/json/dynamic-inl.h> // @shim
@@ -14,4 +14,4 @@ * limitations under the License. */ -#include <folly/json/dynamic.h>+#include <folly/json/dynamic.h> // @shim
@@ -37,6 +37,12 @@ true, "CPUThreadPoolExecutor will use ThrottledLifoSem by default"); +FOLLY_GFLAGS_DEFINE_bool(+ folly_cputhreadpoolexecutor_always_dequeue_with_timeout,+ false,+ "Always use BlockingQueue::try_take_for() even when the pool cannot be "+ "scaled down. Only for testing purposes");+ namespace folly { const size_t CPUThreadPoolExecutor::kDefaultMaxQueueSize = 1 << 14;@@ -46,9 +52,7 @@ std::chrono::milliseconds expiration, Func&& expireCallback, int8_t pri)- : Task(std::move(f), expiration, std::move(expireCallback), pri) {- DCHECK(func_); // Empty func reserved as poison.-}+ : Task(std::move(f), expiration, std::move(expireCallback), pri) {} CPUThreadPoolExecutor::CPUTask::CPUTask() : Task(nullptr, std::chrono::milliseconds(0), nullptr) {}@@ -221,7 +225,10 @@ void CPUThreadPoolExecutor::add( Func func, std::chrono::milliseconds expiration, Func expireCallback) {- addImpl<false>(std::move(func), 0, expiration, std::move(expireCallback));+ CPUTask task(std::move(func), expiration, std::move(expireCallback), 0);+ addImpl(+ [this](auto&& t) { return taskQueue_->add(std::move(t)); },+ std::move(task)); } void CPUThreadPoolExecutor::addWithPriority(Func func, int8_t priority) {@@ -233,52 +240,15 @@ int8_t priority, std::chrono::milliseconds expiration, Func expireCallback) {- addImpl<true>(- std::move(func), priority, expiration, std::move(expireCallback));-}--template <bool withPriority>-void CPUThreadPoolExecutor::addImpl(- Func func,- int8_t priority,- std::chrono::milliseconds expiration,- Func expireCallback) {- if (!func) {- // Reserve empty funcs as poison by logging the error inline.- invokeCatchingExns("ThreadPoolExecutor: func", std::move(func));- return;- }-- if (withPriority) {- CHECK_GT(getNumPriorities(), 0);- }-+ CHECK_GT(getNumPriorities(), 0); CPUTask task( std::move(func), expiration, std::move(expireCallback), priority);- if (auto queueObserver = getQueueObserver(priority)) {- task.queueObserverPayload_ = queueObserver->onEnqueued(task.context_.get());- }- registerTaskEnqueue(task);-- // It's not safe to expect that the executor is alive after a task is added to- // the queue (this task could be holding the last KeepAlive and when finished- // - it may unblock the executor shutdown).- // If we need executor to be alive after adding into the queue, we have to- // acquire a KeepAlive.- bool mayNeedToAddThreads = minThreads_.load(std::memory_order_relaxed) == 0 ||- activeThreads_.load(std::memory_order_relaxed) <- maxThreads_.load(std::memory_order_relaxed);- folly::Executor::KeepAlive<> ka = mayNeedToAddThreads- ? getKeepAliveToken(this)- : folly::Executor::KeepAlive<>{};-- auto result = withPriority- ? taskQueue_->addWithPriority(std::move(task), priority)- : taskQueue_->add(std::move(task));-- if (mayNeedToAddThreads && !result.reusedThread) {- ensureActiveThreads();- }+ addImpl(+ [this](auto&& t) {+ auto pri = t.priority();+ return taskQueue_->addWithPriority(std::move(t), pri);+ },+ std::move(task)); } uint8_t CPUThreadPoolExecutor::getNumPriorities() const {@@ -298,25 +268,30 @@ return taskQueue_.get(); } -// threadListLock_ must be writelocked.-bool CPUThreadPoolExecutor::tryDecrToStop() {- auto toStop = threadsToStop_.load(std::memory_order_relaxed);- if (toStop <= 0) {- return false;- }- threadsToStop_.store(toStop - 1, std::memory_order_relaxed);+// Does not need threadListLock_ lock.+bool CPUThreadPoolExecutor::shouldStopThread(bool isPoison) {+ auto threadsToStop = threadsToStop_.load(std::memory_order_relaxed);+ do {+ if (threadsToStop == 0 ||+ // If we're joining, do not allow early stopping: only stop threads when+ // a poison task is received.+ (!isPoison && isJoin_.load(std::memory_order_acquire))) {+ return false;+ }+ } while (!threadsToStop_.compare_exchange_weak(+ threadsToStop, threadsToStop - 1, std::memory_order_relaxed)); return true; } -bool CPUThreadPoolExecutor::taskShouldStop(folly::Optional<CPUTask>& task) {- if (tryDecrToStop()) {- return true;- }- if (task) {- return false;- } else {- return tryTimeoutThread();+// threadListLock_ must be writelocked.+void CPUThreadPoolExecutor::stopThread(const ThreadPtr& thread) {+ for (auto& o : observers_) {+ o->threadStopped(thread.get()); }+ stoppedThreadProcessedTasks_ += thread->processedTasks;+ thread->processedTasks = 0;+ threadList_.remove(thread);+ stoppedThreads_.add(folly::copy(thread)); } void CPUThreadPoolExecutor::threadRun(ThreadPtr thread) {@@ -337,24 +312,23 @@ threadIdCollector_->removeTid(folly::getOSThreadID()); }); while (true) {- auto task = taskQueue_->try_take_for(- threadTimeout_.load(std::memory_order_relaxed));+ auto task =+ (threadsCanTimeout_.load(std::memory_order_relaxed) ||+ FLAGS_folly_cputhreadpoolexecutor_always_dequeue_with_timeout)+ ? taskQueue_->try_take_for(+ threadTimeout_.load(std::memory_order_relaxed))+ : taskQueue_->take(); - // Handle thread stopping, either by task timeout, or- // by 'poison' task added in join() or stop().- if (FOLLY_UNLIKELY(!task || !task->func_)) {- // Actually remove the thread from the list.+ // Handle thread stopping, either by task timeout, or by 'poison' task added+ // by stopThreads().+ if (bool timeout = !task; FOLLY_UNLIKELY(timeout || !task->func_)) { std::unique_lock w{threadListLock_};- if (taskShouldStop(task)) {- for (auto& o : observers_) {- o->threadStopped(thread.get());- }- threadList_.remove(thread);- stoppedThreads_.add(thread);+ if (shouldStopThread(/* isPoison */ !timeout) ||+ (timeout && tryTimeoutThread())) {+ stopThread(thread); return;- } else {- continue; }+ continue; } if (auto queueObserver = getQueueObserver(task->priority())) {@@ -362,13 +336,10 @@ } runTask(thread, std::move(task.value())); - if (FOLLY_UNLIKELY(threadsToStop_ > 0 && !isJoin_)) {+ if (shouldStopThread(/* isPoison */ false)) { std::unique_lock w{threadListLock_};- if (tryDecrToStop()) {- threadList_.remove(thread);- stoppedThreads_.add(thread);- return;- }+ stopThread(thread);+ return; } } }
@@ -30,34 +30,27 @@ /** * A Thread pool for CPU bound tasks. *- * @note A single queue backed by folly/LifoSem and folly/MPMC queue.- * Because of this contention can be quite high,- * since all the worker threads and all the producer threads hit- * the same queue. MPMC queue excels in this situation but dictates a max queue- * size.- *- * @note The default queue throws when full (folly::QueueBehaviorIfFull::THROW),- * so add() can fail. Furthermore, join() can also fail if the queue is full,- * because it enqueues numThreads poison tasks to stop the threads. If join() is- * needed to be guaranteed to succeed PriorityLifoSemMPMCQueue can be used- * instead, initializing the lowest priority's (LO_PRI) capacity to at least- * numThreads. Poisons use LO_PRI so if that priority is not used for any user- * task join() is guaranteed not to encounter a full queue.+ * @note A single queue backed by:+ * - An efficient semaphore, such as:+ * - folly::LifoSem+ * - folly::ThrottledlifoSem+ * * An efficient unbounded concurrent queue, such as:+ * - folly::UMPMCQueue+ * Therefore, this thread pool scales to very high levels of concurrent access. *- * @note If a blocking queue (folly::QueueBehaviorIfFull::BLOCK) is used, and+ * @note If a bounded queue (folly::QueueBehaviorIfFull::BLOCK) is used, and * tasks executing on a given thread pool schedule more tasks, deadlock is- * possible if the queue becomes full. Deadlock is also possible if there is+ * possible if the queue becomes full. Deadlock is also possible if there is * a circular dependency among multiple thread pools with blocking queues.- * To avoid this situation, use non-blocking queue(s), or schedule tasks only- * from threads not belonging to the given thread pool(s), or use- * folly::IOThreadPoolExecutor.+ * To avoid this situation, either use non-blocking queue(s) only (default and+ * recommended), or schedule tasks only from threads not belonging to the given+ * thread pool(s). *- * @note LifoSem wakes up threads in Lifo order - i.e. there are only few- * threads as necessary running, and we always try to reuse the same few threads- * for better cache locality.- * All Folly BlockingQueue implementations use either LifoSem or- * ThrottledLifoSem, which madvise away the stack of threads that are inactive- * for a long time.+ * @note LifoSem and ThrottledLifoSem wake up threads in LIFO order - i.e. there+ * are only ever as few threads as necessary actually running, and we always try+ * to reuse the same few threads for better cache locality. The other threads+ * are be suspended continuously until they are needed to handle spikes in work,+ * and their stacks would be madvised away while the threads are suspended. * * @note Supports priorities - priorities are implemented as multiple queues - * each worker thread checks the highest priority queue first. Threads@@ -185,6 +178,9 @@ protected: BlockingQueue<CPUTask>* FOLLY_NONNULL getTaskQueue();+ template <typename EnqueueTask>+ void addImpl(EnqueueTask&& enqueueTask, CPUTask&& task);+ std::unique_ptr<ThreadIdWorkerProvider> threadIdCollector_{ std::make_unique<ThreadIdWorkerProvider>()}; @@ -193,15 +189,8 @@ void stopThreads(size_t n) override; size_t getPendingTaskCountImpl() const override final; - bool tryDecrToStop();- bool taskShouldStop(folly::Optional<CPUTask>&);-- template <bool withPriority>- void addImpl(- Func func,- int8_t priority,- std::chrono::milliseconds expiration,- Func expireCallback);+ bool shouldStopThread(bool isPoison);+ void stopThread(const ThreadPtr& thread); std::unique_ptr<folly::QueueObserverFactory> createQueueObserverFactory(); QueueObserver* FOLLY_NULLABLE getQueueObserver(int8_t pri);@@ -211,8 +200,40 @@ std::array<std::atomic<folly::QueueObserver*>, UCHAR_MAX + 1> queueObservers_; std::unique_ptr<folly::QueueObserverFactory> queueObserverFactory_{ createQueueObserverFactory()};- std::atomic<ssize_t> threadsToStop_{0};+ std::atomic<size_t> threadsToStop_{0}; Options::Blocking prohibitBlockingOnThreadPools_ = Options::Blocking::allow; };++template <typename EnqueueTask>+void CPUThreadPoolExecutor::addImpl(EnqueueTask&& enqueueTask, CPUTask&& task) {+ if (!task.func_) {+ // Reserve empty funcs as poison by logging the error inline.+ invokeCatchingExns("ThreadPoolExecutor: func", std::move(task.func_));+ return;+ }++ if (auto queueObserver = getQueueObserver(task.priority())) {+ task.queueObserverPayload_ = queueObserver->onEnqueued(task.context_.get());+ }+ registerTaskEnqueue(task);++ // It's not safe to expect that the executor is alive after a task is added to+ // the queue (this task could be holding the last KeepAlive and when finished+ // - it may unblock the executor shutdown).+ // If we need executor to be alive after adding into the queue, we have to+ // acquire a KeepAlive.+ bool mayNeedToAddThreads = minThreads_.load(std::memory_order_relaxed) == 0 ||+ activeThreads_.load(std::memory_order_relaxed) <+ maxThreads_.load(std::memory_order_relaxed);+ folly::Executor::KeepAlive<> ka = mayNeedToAddThreads+ ? getKeepAliveToken(this)+ : folly::Executor::KeepAlive<>{};++ auto result = enqueueTask(std::move(task));++ if (mayNeedToAddThreads && !result.reusedThread) {+ ensureActiveThreads();+ }+} } // namespace folly
@@ -31,9 +31,10 @@ namespace folly { Codel::Codel()- : Codel(Codel::Options()- .setInterval(milliseconds(FLAGS_codel_interval))- .setTargetDelay(milliseconds(FLAGS_codel_target_delay))) {}+ : Codel(+ Codel::Options()+ .setInterval(milliseconds(FLAGS_codel_interval))+ .setTargetDelay(milliseconds(FLAGS_codel_target_delay))) {} Codel::Codel(const Options& options) : codelMinDelayNs_(0),
@@ -29,7 +29,6 @@ #include <vector> #include <glog/logging.h>-#include <folly/ScopeGuard.h> #include <folly/concurrency/ProcessLocalUniqueId.h> #include <folly/portability/GFlags.h> #include <folly/synchronization/LifoSem.h>@@ -45,15 +44,23 @@ class EDFThreadPoolExecutor::Task { public:- explicit Task(Func&& f, int repeat, uint64_t deadline)- : f_(std::move(f)), total_(repeat), deadline_(deadline) {}+ struct Poison {}; + explicit Task(Func&& f, uint64_t deadline)+ : f_(std::move(f)), total_(1), deadline_(deadline) {}+ explicit Task(std::vector<Func>&& fs, uint64_t deadline) : fs_(std::move(fs)), total_(fs_.size()), deadline_(deadline) {} + explicit Task(Poison, int total) : total_(total), deadline_(kLatestDeadline) {+ CHECK_GT(total, 0);+ }+ uint64_t getDeadline() const { return deadline_; } uint64_t getEnqueueOrder() const { return enqueueOrder_; } + bool isPoison() const { return !f_ && fs_.empty() && total_ > 0; }+ bool isDone() const { return iter_.load(std::memory_order_relaxed) >= total_; }@@ -89,7 +96,7 @@ std::atomic<int> iter_{0}; int total_; uint64_t deadline_;- uint64_t enqueueOrder_;+ uint64_t enqueueOrder_ = 0; std::shared_ptr<RequestContext> context_ = RequestContext::saveContext(); std::chrono::steady_clock::time_point enqueueTime_ = std::chrono::steady_clock::now();@@ -120,9 +127,6 @@ static constexpr std::size_t kNumBuckets = 2 << 5; - explicit TaskQueue()- : buckets_{}, curDeadline_(kLatestDeadline), numItems_(0) {}- void push(TaskPtr task) { auto deadline = task->getDeadline(); auto& bucket = getBucket(deadline);@@ -133,8 +137,6 @@ bucket.empty.store(bucket.tasks.empty(), std::memory_order_relaxed); } - numItems_.fetch_add(1, std::memory_order_seq_cst);- // Update current earliest deadline if necessary uint64_t curDeadline = curDeadline_.load(std::memory_order_relaxed); do {@@ -145,13 +147,10 @@ curDeadline, deadline, std::memory_order_relaxed)); } + // Should only be called on a nonempty queue. TaskPtr pop() { bool needDeadlineUpdate = false; for (;;) {- if (numItems_.load(std::memory_order_seq_cst) == 0) {- return nullptr;- }- auto curDeadline = curDeadline_.load(std::memory_order_relaxed); auto& bucket = getBucket(curDeadline); @@ -191,7 +190,6 @@ // Current task finished. Remove from the queue. bucket.tasks.pop(); bucket.empty.store(bucket.tasks.empty(), std::memory_order_relaxed);- numItems_.fetch_sub(1, std::memory_order_seq_cst); } } @@ -201,8 +199,6 @@ } } - std::size_t size() const { return numItems_.load(std::memory_order_seq_cst); }- private: Bucket& getBucket(uint64_t deadline) { return buckets_[deadline % kNumBuckets];@@ -251,13 +247,7 @@ } std::array<Bucket, kNumBuckets> buckets_;- std::atomic<uint64_t> curDeadline_;-- // All operations performed on `numItems_` explicitly specify memory- // ordering of `std::memory_order_seq_cst`. This is due to `numItems_`- // performing Dekker's algorithm with `numIdleThreads_` prior to consumer- // threads (workers) wait on `sem_`.- std::atomic<std::size_t> numItems_;+ std::atomic<uint64_t> curDeadline_ = kLatestDeadline; }; /* static */ std::unique_ptr<EDFThreadPoolSemaphore>@@ -300,21 +290,11 @@ add(std::move(f), kLatestDeadline); } -void EDFThreadPoolExecutor::add(Func f, std::size_t total, uint64_t deadline) {- if (FOLLY_UNLIKELY(isJoin_.load(std::memory_order_relaxed) || total == 0)) {- return;- }-- auto task = std::make_shared<Task>(std::move(f), total, deadline);+void EDFThreadPoolExecutor::add(Func f, uint64_t deadline) {+ auto task = std::make_shared<Task>(std::move(f), deadline); registerTaskEnqueue(*task); taskQueue_->push(std::move(task));-- auto numIdleThreads = numIdleThreads_.load(std::memory_order_seq_cst);- if (numIdleThreads > 0) {- // If idle threads are available notify them, otherwise all worker threads- // are running and will get around to this task in time.- sem_->post(std::min(total, numIdleThreads));- }+ sem_->post(1); } void EDFThreadPoolExecutor::add(std::vector<Func> fs, uint64_t deadline) {@@ -326,19 +306,34 @@ auto task = std::make_shared<Task>(std::move(fs), deadline); registerTaskEnqueue(*task); taskQueue_->push(std::move(task));-- auto numIdleThreads = numIdleThreads_.load(std::memory_order_seq_cst);- if (numIdleThreads > 0) {- // If idle threads are available notify them, otherwise all worker threads- // are running and will get around to this task in time.- sem_->post(std::min(total, numIdleThreads));- }+ sem_->post(static_cast<uint32_t>(total)); } size_t EDFThreadPoolExecutor::getTaskQueueSize() const {- return taskQueue_->size();+ return sem_->valueGuess(); } +bool EDFThreadPoolExecutor::tryStopThread(+ const ThreadPtr& thread, bool isPoison) {+ auto threadsToStop = threadsToStop_.load(std::memory_order_relaxed);+ do {+ if (threadsToStop == 0 || (!isPoison && isJoin_)) {+ return false;+ }+ } while (!threadsToStop_.compare_exchange_weak(+ threadsToStop, threadsToStop - 1, std::memory_order_relaxed));++ std::unique_lock w{threadListLock_};+ for (auto& o : observers_) {+ o->threadStopped(thread.get());+ }+ stoppedThreadProcessedTasks_ += thread->processedTasks;+ thread->processedTasks = 0;+ threadList_.remove(thread);+ stoppedThreads_.add(folly::copy(thread));+ return true;+}+ void EDFThreadPoolExecutor::threadRun(ThreadPtr thread) { this->threadPoolHook_.registerThread(); ExecutorBlockingGuard guard{@@ -346,24 +341,23 @@ thread->startupBaton.post(); for (;;) {- auto task = take();+ sem_->wait(); - // Handle thread stopping- if (FOLLY_UNLIKELY(!task)) {- // Actually remove the thread from the list.- std::unique_lock w{threadListLock_};- for (auto& o : observers_) {- o->threadStopped(thread.get());- }- threadList_.remove(thread);- stoppedThreads_.add(thread);- return;- }+ // We consumed a post so the queue is non-empty, but we need to discard+ // finished tasks.+ int iter;+ std::shared_ptr<EDFThreadPoolExecutor::Task> task;+ do {+ task = taskQueue_->pop();+ iter = task->next();+ } while (iter < 0); - int iter = task->next();- if (FOLLY_UNLIKELY(iter < 0)) {- // This task is already finished- continue;+ if (task->isPoison()) {+ if (tryStopThread(thread, /* isPoison */ true)) {+ return;+ } else {+ continue; // Poison was consumed early by tryStopThread().+ } } thread->idle.store(false, std::memory_order_relaxed);@@ -401,75 +395,32 @@ observer.taskProcessed(taskInfo); }); + thread->processedTasks = thread->processedTasks + 1;+ thread->idle.store(true, std::memory_order_relaxed); thread->lastActiveTime.store( std::chrono::steady_clock::now(), std::memory_order_relaxed);++ if (tryStopThread(thread, /* isPoison */ false)) {+ return;+ } } } // threadListLock_ is writelocked. void EDFThreadPoolExecutor::stopThreads(std::size_t numThreads) {+ if (numThreads == 0) {+ return;+ } threadsToStop_.fetch_add(numThreads, std::memory_order_relaxed);+ taskQueue_->push(+ std::make_shared<Task>(Task::Poison{}, static_cast<int>(numThreads))); sem_->post(numThreads); } // threadListLock_ is read (or write) locked. std::size_t EDFThreadPoolExecutor::getPendingTaskCountImpl() const { return getTaskQueueSize();-}--bool EDFThreadPoolExecutor::shouldStop() {- // in normal cases, only do a read (prevents cache line bounces)- if (threadsToStop_.load(std::memory_order_relaxed) <= 0 ||- isJoin_.load(std::memory_order_relaxed)) {- return false;- }- // modify only if needed- if (threadsToStop_.fetch_sub(1, std::memory_order_relaxed) > 0) {- return true;- } else {- threadsToStop_.fetch_add(1, std::memory_order_relaxed);- return false;- }-}--std::shared_ptr<EDFThreadPoolExecutor::Task> EDFThreadPoolExecutor::take() {- if (FOLLY_UNLIKELY(shouldStop())) {- return nullptr;- }-- if (auto task = taskQueue_->pop()) {- return task;- }-- if (FOLLY_UNLIKELY(isJoin_.load(std::memory_order_relaxed))) {- return nullptr;- }-- // No tasks on the horizon, so go sleep- numIdleThreads_.fetch_add(1, std::memory_order_seq_cst);-- SCOPE_EXIT {- numIdleThreads_.fetch_sub(1, std::memory_order_seq_cst);- };-- for (;;) {- if (FOLLY_UNLIKELY(shouldStop())) {- return nullptr;- }-- if (auto task = taskQueue_->pop()) {- // It's possible to return a finished task here, in which case- // the worker will call this function again.- return task;- }-- if (FOLLY_UNLIKELY(isJoin_.load(std::memory_order_relaxed))) {- return nullptr;- }-- sem_->wait();- } } void EDFThreadPoolExecutor::fillTaskInfo(const Task& task, TaskInfo& info) {
@@ -32,6 +32,7 @@ virtual ~EDFThreadPoolSemaphore() = default; virtual void post(uint32_t value) = 0; virtual void wait() = 0;+ virtual uint32_t valueGuess() const = 0; }; template <class Semaphore>@@ -43,6 +44,7 @@ void post(uint32_t value) override { sem_.post(value); } void wait() override { sem_.wait(); }+ uint32_t valueGuess() const override { return sem_.valueGuess(); } private: Semaphore sem_;@@ -78,11 +80,10 @@ ~EDFThreadPoolExecutor() override; - using SoftRealTimeExecutor::add; using ThreadPoolExecutor::add; void add(Func f) override;- void add(Func f, std::size_t total, uint64_t deadline) override;+ void add(Func f, uint64_t deadline) override; void add(std::vector<Func> fs, uint64_t deadline) override; size_t getTaskQueueSize() const;@@ -93,8 +94,7 @@ std::size_t getPendingTaskCountImpl() const override final; private:- bool shouldStop();- std::shared_ptr<Task> take();+ bool tryStopThread(const ThreadPtr& thread, bool isPoison); void fillTaskInfo(const Task& task, TaskInfo& info); void registerTaskEnqueue(const Task& task);@@ -102,12 +102,6 @@ std::unique_ptr<TaskQueue> taskQueue_; std::unique_ptr<EDFThreadPoolSemaphore> sem_; std::atomic<int> threadsToStop_{0};-- // All operations performed on `numIdleThreads_` explicitly specify memory- // ordering of `std::memory_order_seq_cst`. This is due to `numIdleThreads_`- // performing Dekker's algorithm with `numItems` prior to consumer threads- // (workers) wait on `sem_`.- std::atomic<std::size_t> numIdleThreads_{0}; }; } // namespace folly
@@ -41,9 +41,8 @@ }; // Constant time size = false to support auto_unlink behavior, options are // mutually exclusive- typedef boost::intrusive::- list<ExecutionObserver, boost::intrusive::constant_time_size<false>>- List;+ using List = boost::intrusive::+ list<ExecutionObserver, boost::intrusive::constant_time_size<false>>; virtual ~ExecutionObserver() = default;
@@ -245,13 +245,19 @@ auto it = functionsMap_.find(nameID); // check if the nameID is unique if (it != functionsMap_.end()) {- throw std::invalid_argument(to<std::string>(- "FunctionScheduler: a function named \"", nameID, "\" already exists"));+ throw std::invalid_argument(+ to<std::string>(+ "FunctionScheduler: a function named \"",+ nameID,+ "\" already exists")); } if (currentFunction_ && currentFunction_->name == nameID) {- throw std::invalid_argument(to<std::string>(- "FunctionScheduler: a function named \"", nameID, "\" already exists"));+ throw std::invalid_argument(+ to<std::string>(+ "FunctionScheduler: a function named \"",+ nameID,+ "\" already exists")); } addFunctionToHeap(@@ -426,6 +432,18 @@ return true; } +// Use a dedicated method for sleeping in the main loop, so it can be identified+// easily in profiles and traces.+FOLLY_NOINLINE void FunctionScheduler::waitForWork(+ std::unique_lock<std::mutex>& lock) {+ runningCondvar_.wait(lock);+}+FOLLY_NOINLINE void FunctionScheduler::waitForWork(+ std::unique_lock<std::mutex>& lock,+ std::chrono::steady_clock::duration timeout) {+ runningCondvar_.wait_for(lock, timeout);+}+ void FunctionScheduler::run() { std::unique_lock lock(mutex_); @@ -435,7 +453,7 @@ // If we have nothing to run, wait until a function is added or until we // are stopped. if (functions_.empty()) {- runningCondvar_.wait(lock);+ waitForWork(lock); continue; } @@ -446,7 +464,7 @@ runOneFunction(lock, now); runningCondvar_.notify_all(); } else {- runningCondvar_.wait_for(lock, sleepTime);+ waitForWork(lock, sleepTime); } } }
@@ -349,6 +349,11 @@ void clearHeap(); + void waitForWork(std::unique_lock<std::mutex>& lock);+ void waitForWork(+ std::unique_lock<std::mutex>& lock,+ std::chrono::steady_clock::duration timeout);+ std::thread thread_; // Mutex to protect our member variables.
@@ -59,7 +59,7 @@ Singleton<std::shared_ptr<ImmutableGlobalCPUExecutor>, GlobalTag> gImmutableGlobalCPUExecutor([] { size_t nthreads = FLAGS_folly_global_cpu_executor_threads;- nthreads = nthreads ? nthreads : folly::hardware_concurrency();+ nthreads = nthreads ? nthreads : folly::available_concurrency(); return new std::shared_ptr<ImmutableGlobalCPUExecutor>( new ImmutableGlobalCPUExecutor( nthreads,@@ -69,7 +69,7 @@ Singleton<std::shared_ptr<IOThreadPoolExecutor>, GlobalTag> gImmutableGlobalIOExecutor([] { size_t nthreads = FLAGS_folly_global_io_executor_threads;- nthreads = nthreads ? nthreads : folly::hardware_concurrency();+ nthreads = nthreads ? nthreads : folly::available_concurrency(); return new std::shared_ptr<IOThreadPoolExecutor>(new IOThreadPoolExecutor( nthreads, std::make_shared<NamedThreadFactory>("GlobalIOThreadPool")));
@@ -43,7 +43,7 @@ template <class T> class IOObjectCache { public:- typedef std::function<std::shared_ptr<T>(folly::EventBase*)> TFactory;+ using TFactory = std::function<std::shared_ptr<T>(folly::EventBase*)>; IOObjectCache() = default; explicit IOObjectCache(TFactory factory) : factory_(std::move(factory)) {}
@@ -66,9 +66,11 @@ idleTimeout = MemoryIdler::getVariationTimeout(idleTimeout); - scheduleTimeout(static_cast<uint32_t>(- std::chrono::duration_cast<std::chrono::milliseconds>(idleTimeout)- .count()));+ scheduleTimeout(+ static_cast<uint32_t>(+ std::chrono::duration_cast<std::chrono::milliseconds>(+ idleTimeout)+ .count())); } else { num_++; }@@ -286,7 +288,9 @@ } } for (const auto& thread : stoppedThreads) {- stoppedThreads_.add(thread);+ stoppedThreadProcessedTasks_ += thread->processedTasks;+ thread->processedTasks = 0;+ stoppedThreads_.add(folly::copy(thread)); threadList_.remove(thread); } }
@@ -131,9 +131,22 @@ } void keepAliveRelease() noexcept override {- if (keepAliveCount_.fetch_sub(1, std::memory_order_acq_rel) == 1) {- add([] {});- }+ auto keepAliveCount = keepAliveCount_.load(std::memory_order_relaxed);+ do {+ DCHECK(keepAliveCount > 0);+ if (keepAliveCount == 1) {+ add([this] {+ // the final count *must* be released from this executor or else if we+ // are mid-destructor we have a data race+ keepAliveCount_.fetch_sub(1, std::memory_order_relaxed);+ });+ return;+ }+ } while (!keepAliveCount_.compare_exchange_weak(+ keepAliveCount,+ keepAliveCount - 1,+ std::memory_order_release,+ std::memory_order_relaxed)); } private:
@@ -29,8 +29,8 @@ public: // Reality is that better than millisecond resolution is very hard to // achieve. However, we reserve the right to be incredible.- typedef std::chrono::microseconds Duration;- typedef std::chrono::steady_clock::time_point TimePoint;+ using Duration = std::chrono::microseconds;+ using TimePoint = std::chrono::steady_clock::time_point; ~ScheduledExecutor() override = default;
@@ -20,7 +20,6 @@ #include <memory> #include <mutex> -#include <folly/concurrency/UnboundedQueue.h> #include <folly/executors/GlobalExecutor.h> #include <folly/executors/SerializedExecutor.h> #include <folly/io/async/Request.h>@@ -70,6 +69,8 @@ static KeepAlive<SerialExecutorImpl> create( KeepAlive<Executor> parent = getGlobalCPUExecutor()); + const KeepAlive<Executor>& parent() const { return parent_; }+ class Deleter { public: Deleter() {}@@ -135,46 +136,20 @@ Queue<Task> queue_; }; -template <int LgQueueSegmentSize = 8>-struct SerialExecutorWithUnboundedQueue {- // The consumer should only dequeue when the queue is non-empty, so we don't- // need blocking.- template <typename Task>- using queue =- folly::UMPSCQueue<Task, /* MayBlock */ false, LgQueueSegmentSize>;- using type = SerialExecutorImpl<queue>;-};- class NoopMutex; template <class Task, class Mutex = folly::DistributedMutex> class SerialExecutorMPSCQueue; template <typename Task>-using SmallSerialExecutorQueue = SerialExecutorMPSCQueue<Task>;+using SerialExecutorQueue = SerialExecutorMPSCQueue<Task>; template <typename Task> using SPSerialExecutorQueue = SerialExecutorMPSCQueue<Task, NoopMutex>; } // namespace detail -using SerialExecutor =- typename detail::SerialExecutorWithUnboundedQueue<>::type;--template <int LgQueueSegmentSize>-using SerialExecutorWithLgSegmentSize =- typename detail::SerialExecutorWithUnboundedQueue<LgQueueSegmentSize>::type;--/**- * SerialExecutor implementation that uses a mutex-protected queue. This uses- * significantly less memory than SerialExecutor, at the expense of being more- * susceptible to contention on add(). This is intended for use cases where- * granular SerialExecutors are required, for example one per request. In these- * scenarios, there are not many concurrent submitters, so contention is not an- * issue, while memory overhead is.- */-using SmallSerialExecutor =- detail::SerialExecutorImpl<detail::SmallSerialExecutorQueue>;+using SerialExecutor = detail::SerialExecutorImpl<detail::SerialExecutorQueue>; /** * Single-producer version of SmallExecutor. It is the responsibility of the
@@ -29,7 +29,7 @@ // means that not every deadline is guaranteed to be met. class SoftRealTimeExecutor : public virtual Executor { public:- void add(Func) override = 0;+ using Executor::add; // Add a task with an assigned abstract deadline. //@@ -40,11 +40,7 @@ // rate-monotonic scheduling that prioritizes small tasks. It also enables, // for example, tiered scheduling (strictly prioritizing a category of tasks) // by assigning the high-bit of the deadline.- void add(Func func, uint64_t deadline) {- add(std::move(func), /* total */ 1, deadline);- }-- virtual void add(Func, std::size_t total, uint64_t deadline) = 0;+ virtual void add(Func func, uint64_t deadline) = 0; virtual void add(std::vector<Func>, uint64_t deadline) = 0; folly::Executor::KeepAlive<> deadlineExecutor(uint64_t deadline);
@@ -60,20 +60,22 @@ } void StrandContext::add(Func func, Executor::KeepAlive<> executor) {- addImpl(QueueItem{- std::move(func),- std::move(executor),- folly::none,- RequestContext::saveContext()});+ addImpl(+ QueueItem{+ std::move(func),+ std::move(executor),+ folly::none,+ RequestContext::saveContext()}); } void StrandContext::addWithPriority( Func func, Executor::KeepAlive<> executor, int8_t priority) {- addImpl(QueueItem{- std::move(func),- std::move(executor),- priority,- RequestContext::saveContext()});+ addImpl(+ QueueItem{+ std::move(func),+ std::move(executor),+ priority,+ RequestContext::saveContext()}); } void StrandContext::addImpl(QueueItem&& item) {
@@ -0,0 +1,209 @@+/*+ * Copyright (c) Meta Platforms, Inc. and affiliates.+ *+ * Licensed under the Apache License, Version 2.0 (the "License");+ * you may not use this file except in compliance with the License.+ * You may obtain a copy of the License at+ *+ * http://www.apache.org/licenses/LICENSE-2.0+ *+ * Unless required by applicable law or agreed to in writing, software+ * distributed under the License is distributed on an "AS IS" BASIS,+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.+ * See the License for the specific language governing permissions and+ * limitations under the License.+ */++#include <folly/executors/StripedEDFThreadPoolExecutor.h>++#include <optional>++#include <glog/logging.h>+#include <folly/concurrency/CacheLocality.h>+#include <folly/container/IntrusiveHeap.h>+#include <folly/synchronization/DistributedMutex.h>+#include <folly/synchronization/StripedThrottledLifoSem.h>++namespace folly {++namespace {++template <class T>+class EDFPriorityQueue {+ public:+ EDFPriorityQueue() = default;++ ~EDFPriorityQueue() {+ while (!heap_.empty()) {+ delete heap_.pop();+ }+ }++ void enqueue(T&& item, uint64_t deadline) {+ std::unique_ptr<Node> node{new Node{std::move(item), deadline}};+ mutex_.lock_combine([&] { heap_.push(node.release()); });+ }++ T dequeue() {+ std::unique_ptr<Node> node;+ mutex_.lock_combine([&] { node.reset(heap_.pop()); });+ CHECK(node);+ return std::move(node->item);+ }++ private:+ struct Node : IntrusiveHeapNode<> {+ Node(T&& it, uint64_t d) : item(std::move(it)), deadline(d) {}++ T item;+ uint64_t deadline;++ friend bool operator<(const Node& lhs, const Node& rhs) {+ return lhs.deadline > rhs.deadline;+ }+ };++ EDFPriorityQueue(const EDFPriorityQueue&) = delete;+ EDFPriorityQueue(EDFPriorityQueue&&) = delete;+ EDFPriorityQueue& operator=(const EDFPriorityQueue&) = delete;+ EDFPriorityQueue& operator=(EDFPriorityQueue&&) = delete;++ DistributedMutex mutex_;+ IntrusiveHeap<Node> heap_;+};++template <class T>+class BlockingQueueWithDeadline : public BlockingQueue<T> {+ public:+ virtual BlockingQueueAddResult addWithDeadline(+ T&& item, uint64_t deadline) = 0;+};++template <class T>+class StripedEDFPriorityBlockingQueue final+ : public BlockingQueueWithDeadline<T> {+ public:+ explicit StripedEDFPriorityBlockingQueue(+ const ThrottledLifoSem::Options& options)+ : sem_(LLCAccessSpreader::get().numStripes(), std::tuple{}, options) {+ StripedThrottledLifoSemBalancer::subscribe(sem_);+ }++ ~StripedEDFPriorityBlockingQueue() override {+ StripedThrottledLifoSemBalancer::unsubscribe(sem_);+ }++ BlockingQueueAddResult addWithDeadline(T&& item, uint64_t deadline) override {+ const auto stripeIdx = getStripeIdx();+ sem_.payload(stripeIdx).enqueue(std::move(item), deadline);+ return sem_.post(stripeIdx);+ }++ BlockingQueueAddResult add(T&& item) override {+ return addWithDeadline(+ std::move(item), StripedEDFThreadPoolExecutor::kLatestDeadline);+ }++ T take() override {+ const auto stripeIdx = getStripeIdx();+ auto foundStripeIdx = sem_.wait(stripeIdx);+ return sem_.payload(foundStripeIdx).dequeue();+ }++ folly::Optional<T> try_take_for(std::chrono::milliseconds time) override {+ const auto stripeIdx = getStripeIdx();+ if (auto foundStripeIdx = sem_.try_wait_for(stripeIdx, time)) {+ return sem_.payload(*foundStripeIdx).dequeue();+ }+ return none;+ }++ size_t size() override { return sem_.valueGuess(); }++ private:+ static size_t getStripeIdx() { return LLCAccessSpreader::get().current(); }++ StripedThrottledLifoSem<EDFPriorityQueue<T>> sem_;+};++// Specialized implementation for the numStripes == 1 case, to avoid the+// StripedThrottledLifoSem overhead when we don't need it.+template <class T>+class EDFPriorityBlockingQueue final : public BlockingQueueWithDeadline<T> {+ public:+ explicit EDFPriorityBlockingQueue(const ThrottledLifoSem::Options& options)+ : sem_(options) {}++ BlockingQueueAddResult addWithDeadline(T&& item, uint64_t deadline) override {+ pq_.enqueue(std::move(item), deadline);+ return sem_.post();+ }++ BlockingQueueAddResult add(T&& item) override {+ return addWithDeadline(+ std::move(item), StripedEDFThreadPoolExecutor::kLatestDeadline);+ }++ T take() override {+ sem_.wait();+ return pq_.dequeue();+ }++ folly::Optional<T> try_take_for(std::chrono::milliseconds time) override {+ if (sem_.try_wait_for(time)) {+ return pq_.dequeue();+ }+ return none;+ }++ size_t size() override { return sem_.valueGuess(); }++ private:+ EDFPriorityQueue<T> pq_;+ ThrottledLifoSem sem_;+};++std::unique_ptr<BlockingQueue<CPUThreadPoolExecutor::CPUTask>> makeQueue(+ const StripedEDFThreadPoolExecutor::Options& options) {+ if (LLCAccessSpreader::get().numStripes() > 1) {+ return std::make_unique<+ StripedEDFPriorityBlockingQueue<CPUThreadPoolExecutor::CPUTask>>(+ options.tlsOptions);+ } else {+ return std::make_unique<+ EDFPriorityBlockingQueue<CPUThreadPoolExecutor::CPUTask>>(+ options.tlsOptions);+ }+}++} // namespace++StripedEDFThreadPoolExecutor::StripedEDFThreadPoolExecutor(+ std::pair<size_t, size_t> numThreads,+ std::shared_ptr<ThreadFactory> threadFactory,+ const Options& options)+ : CPUThreadPoolExecutor(+ numThreads, makeQueue(options), std::move(threadFactory)) {}++void StripedEDFThreadPoolExecutor::add(Func f, uint64_t deadline) {+ CPUTask task(std::move(f), {}, {}, 0);+ CPUThreadPoolExecutor::addImpl(+ [this, deadline](auto&& task) {+ using QueueType = BlockingQueueWithDeadline<CPUTask>;+ auto* taskQueue = static_cast<QueueType*>(getTaskQueue());+ DCHECK_EQ(taskQueue, dynamic_cast<QueueType*>(getTaskQueue()));+ return taskQueue->addWithDeadline(std::move(task), deadline);+ },+ std::move(task));+}++void StripedEDFThreadPoolExecutor::add(+ std::vector<Func> fs, uint64_t deadline) {+ // It might be possible to optimize this if we implement post(n) in+ // StripedThrottledLifoSem, but this variant is very rarely used.+ for (auto& f : fs) {+ add(std::move(f), deadline);+ }+}++} // namespace folly
@@ -0,0 +1,67 @@+/*+ * Copyright (c) Meta Platforms, Inc. and affiliates.+ *+ * Licensed under the Apache License, Version 2.0 (the "License");+ * you may not use this file except in compliance with the License.+ * You may obtain a copy of the License at+ *+ * http://www.apache.org/licenses/LICENSE-2.0+ *+ * Unless required by applicable law or agreed to in writing, software+ * distributed under the License is distributed on an "AS IS" BASIS,+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.+ * See the License for the specific language governing permissions and+ * limitations under the License.+ */++#pragma once++#include <folly/executors/CPUThreadPoolExecutor.h>+#include <folly/executors/SoftRealTimeExecutor.h>+#include <folly/synchronization/ThrottledLifoSem.h>++namespace folly {++/**+ * An approximate implementation of an Earliest Deadline First executor.+ *+ * Instead of having a global priority queue, we maintain one independent queue+ * for each LLC cache, to avoid expensive cross-LLC traffic. This implies that+ * the EDF policy is only honored among tasks submitted from CPUs sharing the+ * same LLC. In practice, each LLC should have enough CPUs to make the+ * approximation good enough for most use cases.+ */+class StripedEDFThreadPoolExecutor+ : public SoftRealTimeExecutor,+ public CPUThreadPoolExecutor {+ public:+ struct Options {+ ThrottledLifoSem::Options tlsOptions;+ };++ static constexpr uint64_t kEarliestDeadline = 0;+ static constexpr uint64_t kLatestDeadline =+ std::numeric_limits<uint64_t>::max();++ explicit StripedEDFThreadPoolExecutor(+ size_t numThreads,+ std::shared_ptr<ThreadFactory> threadFactory =+ std::make_shared<NamedThreadFactory>("StripedEDFTP"),+ const Options& options = {})+ : StripedEDFThreadPoolExecutor(+ {numThreads, numThreads}, std::move(threadFactory), options) {}++ explicit StripedEDFThreadPoolExecutor(+ std::pair<size_t, size_t> numThreads,+ std::shared_ptr<ThreadFactory> threadFactory =+ std::make_shared<NamedThreadFactory>("StripedEDFTP"),+ const Options& options = {});++ using CPUThreadPoolExecutor::add;++ // SoftRealTimeExecutor+ void add(Func f, uint64_t deadline) override;+ void add(std::vector<Func> fs, uint64_t deadline) override;+};++} // namespace folly
@@ -168,6 +168,8 @@ observer.taskProcessed(taskInfo); }); + thread->processedTasks = thread->processedTasks + 1;+ thread->idle.store(true, std::memory_order_relaxed); thread->lastActiveTime.store( std::chrono::steady_clock::now(), std::memory_order_relaxed);@@ -217,6 +219,11 @@ minthreads = numThreads; minThreads_.store(numThreads, std::memory_order_relaxed); }+ // threadsCanTimeout_ can only transition from false to true if maxThreads_+ // is increased (in which case minThreads_ will stay the same). So even if+ // the current threads cannot timeout until the next wakeup, all the new+ // threads can timeout, thus we can get down to minThreads_ active threads.+ threadsCanTimeout_.store(minthreads != numThreads); if (active > numThreads) { numThreadsToJoin = active - numThreads; assert(numThreadsToJoin <= active - minthreads);@@ -266,7 +273,8 @@ // threadListLock_ is writelocked void ThreadPoolExecutor::removeThreads(size_t n, bool isJoin) {- isJoin_ = isJoin;+ // Block early thread stopping.+ isJoin_.store(isJoin, std::memory_order_release); stopThreads(n); } @@ -317,6 +325,7 @@ ThreadPoolExecutor::PoolStats stats; size_t activeTasks = 0; size_t idleAlive = 0;+ uint64_t processedTasks = stoppedThreadProcessedTasks_; for (const auto& thread : threadList_.get()) { if (thread->idle.load(std::memory_order_relaxed)) { const std::chrono::nanoseconds idleTime =@@ -326,9 +335,11 @@ } else { activeTasks++; }+ processedTasks += thread->processedTasks; } stats.pendingTaskCount = getPendingTaskCountImpl(); stats.totalTaskCount = stats.pendingTaskCount + activeTasks;+ stats.processedTaskCount = processedTasks; stats.threadCount = maxThreads_.load(std::memory_order_relaxed); stats.activeThreadCount =@@ -395,7 +406,7 @@ } BlockingQueueAddResult ThreadPoolExecutor::StoppedThreadQueue::add(- ThreadPoolExecutor::ThreadPtr item) {+ ThreadPoolExecutor::ThreadPtr&& item) { std::lock_guard guard(mutex_); queue_.push(std::move(item)); return sem_.post();
@@ -32,6 +32,7 @@ #include <folly/portability/GFlags.h> #include <folly/synchronization/AtomicStruct.h> #include <folly/synchronization/Baton.h>+#include <folly/synchronization/RelaxedAtomic.h> namespace folly { @@ -122,9 +123,10 @@ activeThreadCount(0), pendingTaskCount(0), totalTaskCount(0),+ processedTaskCount(0), maxIdleTime(0) {} size_t threadCount, idleThreadCount, activeThreadCount;- uint64_t pendingTaskCount, totalTaskCount;+ uint64_t pendingTaskCount, totalTaskCount, processedTaskCount; std::chrono::nanoseconds maxIdleTime; }; @@ -237,6 +239,11 @@ static std::atomic<uint64_t> nextId; uint64_t id;++ // Number of tasks processed by this worker. Reset to zero when+ // the thread stops.+ folly::relaxed_atomic<uint64_t> processedTasks;+ std::thread handle; std::atomic<bool> idle; folly::AtomicStruct<std::chrono::steady_clock::time_point> lastActiveTime;@@ -360,7 +367,7 @@ class StoppedThreadQueue : public BlockingQueue<ThreadPtr> { public:- BlockingQueueAddResult add(ThreadPtr item) override;+ BlockingQueueAddResult add(ThreadPtr&& item) override; ThreadPtr take() override; size_t size() override; folly::Optional<ThreadPtr> try_take_for(@@ -394,9 +401,15 @@ std::atomic<size_t> maxThreads_{0}; std::atomic<size_t> minThreads_{0}; std::atomic<size_t> activeThreads_{0};+ std::atomic<bool> threadsCanTimeout_{true}; std::atomic<size_t> threadsToJoin_{0}; std::atomic<std::chrono::milliseconds> threadTimeout_;++ // Number of tasks processed by stopped or joined threads. Updated+ // when a thread stops, which preceeds joining. Requires holding+ // the threadListLock_.+ uint64_t stoppedThreadProcessedTasks_{0}; bool joinKeepAliveOnce() { if (!std::exchange(keepAliveJoined_, true)) {
@@ -50,9 +50,9 @@ // Returns true if an existing thread was able to work on it (used // for dynamically sizing thread pools), false otherwise. Return false // if this feature is not supported.- virtual BlockingQueueAddResult add(T item) = 0;+ virtual BlockingQueueAddResult add(T&& item) = 0; virtual BlockingQueueAddResult addWithPriority(- T item, int8_t /* priority */) {+ T&& item, int8_t /* priority */) { return add(std::move(item)); } virtual uint8_t getNumPriorities() { return 1; }
@@ -34,7 +34,7 @@ const typename Semaphore::Options& semaphoreOptions = {}) : sem_(semaphoreOptions), queue_(max_capacity) {} - BlockingQueueAddResult add(T item) override {+ BlockingQueueAddResult add(T&& item) override { switch (kBehavior) { // static case QueueBehaviorIfFull::THROW: if (!queue_.writeIfNotFull(std::move(item))) {@@ -49,26 +49,26 @@ } T take() override {+ sem_.wait(); T item; while (!queue_.readIfNotEmpty(item)) {- sem_.wait(); } return item; } folly::Optional<T> try_take_for(std::chrono::milliseconds time) override {+ if (!sem_.try_wait_for(time)) {+ return folly::none;+ } T item; while (!queue_.readIfNotEmpty(item)) {- if (!sem_.try_wait_for(time)) {- return folly::none;- } } return item; } size_t capacity() { return queue_.capacity(); } - size_t size() override { return queue_.size(); }+ size_t size() override { return sem_.valueGuess(); } private: Semaphore sem_;
@@ -63,11 +63,11 @@ uint8_t getNumPriorities() override { return queues_.size(); } // Add at medium priority by default- BlockingQueueAddResult add(T item) override {+ BlockingQueueAddResult add(T&& item) override { return addWithPriority(std::move(item), folly::Executor::MID_PRI); } - BlockingQueueAddResult addWithPriority(T item, int8_t priority) override {+ BlockingQueueAddResult addWithPriority(T&& item, int8_t priority) override { int mid = getNumPriorities() / 2; size_t queue = priority < 0 ? std::max(0, mid + priority)@@ -87,24 +87,24 @@ } T take() override {+ sem_.wait(); T item; while (true) { if (nonBlockingTake(item)) { return item; }- sem_.wait(); } } folly::Optional<T> try_take_for(std::chrono::milliseconds time) override {+ if (!sem_.try_wait_for(time)) {+ return folly::none;+ } T item; while (true) { if (nonBlockingTake(item)) { return item; }- if (!sem_.try_wait_for(time)) {- return folly::none;- } } } @@ -117,21 +117,7 @@ return false; } - size_t size() override {- size_t size = 0;- for (auto& q : queues_) {- size += q.size();- }- return size;- }-- size_t sizeGuess() const {- size_t size = 0;- for (auto& q : queues_) {- size += q.sizeGuess();- }- return size;- }+ size_t size() override { return sem_.valueGuess(); } private: Semaphore sem_;
@@ -38,11 +38,11 @@ uint8_t getNumPriorities() override { return queue_.priorities(); } // Add at medium priority by default- BlockingQueueAddResult add(T item) override {+ BlockingQueueAddResult add(T&& item) override { return addWithPriority(std::move(item), folly::Executor::MID_PRI); } - BlockingQueueAddResult addWithPriority(T item, int8_t priority) override {+ BlockingQueueAddResult addWithPriority(T&& item, int8_t priority) override { queue_.at_priority(translatePriority(priority)).enqueue(std::move(item)); return sem_.post(); }@@ -66,9 +66,7 @@ return dequeue(); } - size_t size() override { return queue_.size(); }-- size_t sizeGuess() const { return queue_.size(); }+ size_t size() override { return sem_.valueGuess(); } private: size_t translatePriority(int8_t const priority) {
@@ -0,0 +1,131 @@+/*+ * Copyright (c) Meta Platforms, Inc. and affiliates.+ *+ * Licensed under the Apache License, Version 2.0 (the "License");+ * you may not use this file except in compliance with the License.+ * You may obtain a copy of the License at+ *+ * http://www.apache.org/licenses/LICENSE-2.0+ *+ * Unless required by applicable law or agreed to in writing, software+ * distributed under the License is distributed on an "AS IS" BASIS,+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.+ * See the License for the specific language governing permissions and+ * limitations under the License.+ */++#pragma once++#include <folly/ConstexprMath.h>+#include <folly/Executor.h>+#include <folly/GLog.h>+#include <folly/concurrency/CacheLocality.h>+#include <folly/concurrency/PriorityUnboundedQueueSet.h>+#include <folly/executors/task_queue/BlockingQueue.h>+#include <folly/executors/task_queue/PriorityUnboundedBlockingQueue.h>+#include <folly/lang/Exception.h>+#include <folly/synchronization/StripedThrottledLifoSem.h>++namespace folly {++/**+ * A BlockingQueue sharded by LLC cache. Compared to the default+ * PriorityUnboundedBlockingQueue, this can reduce contention on systems with a+ * large number of LLC caches, at the cost of unfairness and work conservation;+ * see the StripedThrottledLifoSem documentation for a detailed explanation.+ *+ * This queue should preferably be used on thread pools with numThreads equal to+ * the number of available cores, in order to guarantee good balancing across+ * the LLCs. For the same reason, dynamic resizing should be disabled.+ *+ * NOTE: StripeGetter is customizable only for testing purposes.+ */+template <typename T, class StripeGetter = LLCAccessSpreader>+class StripedPriorityUnboundedBlockingQueue : public BlockingQueue<T> {+ struct PrivateTag {};++ public:+ static std::unique_ptr<BlockingQueue<T>> create(+ uint8_t numPriorities,+ const typename ThrottledLifoSem::Options& semaphoreOptions = {}) {+ const auto numStripes = StripeGetter::get().numStripes();+ if (numStripes == 1) {+ // Don't need the overhead, just use the non-striped version.+ return std::make_unique<+ PriorityUnboundedBlockingQueue<T, folly::ThrottledLifoSem>>(+ numPriorities, semaphoreOptions);+ }+ return std::make_unique<+ StripedPriorityUnboundedBlockingQueue<T, StripeGetter>>(+ PrivateTag{}, numStripes, numPriorities, semaphoreOptions);+ }++ // Private, use create().+ StripedPriorityUnboundedBlockingQueue(+ PrivateTag,+ size_t numStripes,+ uint8_t numPriorities,+ const typename ThrottledLifoSem::Options& semaphoreOptions = {})+ : numPriorities_(numPriorities),+ sem_(numStripes, std::tuple{numPriorities_}, semaphoreOptions) {+ StripedThrottledLifoSemBalancer::subscribe(sem_);+ }++ ~StripedPriorityUnboundedBlockingQueue() override {+ StripedThrottledLifoSemBalancer::unsubscribe(sem_);+ }++ uint8_t getNumPriorities() override { return numPriorities_; }++ BlockingQueueAddResult add(T&& item) override {+ return addWithPriority(std::move(item), folly::Executor::MID_PRI);+ }++ BlockingQueueAddResult addWithPriority(T&& item, int8_t priority) override {+ auto stripeIdx = getStripeIdx();+ sem_.payload(stripeIdx)+ .at_priority(translatePriority(priority))+ .enqueue(std::move(item));+ return sem_.post(stripeIdx);+ }++ T take() override {+ const auto stripeIdx = getStripeIdx();+ auto foundStripeIdx = sem_.wait(stripeIdx);+ return dequeue(foundStripeIdx);+ }++ folly::Optional<T> try_take_for(std::chrono::milliseconds time) override {+ const auto stripeIdx = getStripeIdx();+ if (auto foundStripeIdx = sem_.try_wait_for(stripeIdx, time)) {+ return dequeue(*foundStripeIdx);+ }+ return none;+ }++ size_t size() override { return sem_.valueGuess(); }++ private:+ size_t translatePriority(int8_t const priority) {+ int8_t const hi = (numPriorities_ + 1) / 2 - 1;+ int8_t const lo = hi - (numPriorities_ - 1);+ return hi - constexpr_clamp(priority, lo, hi);+ }++ T dequeue(size_t stripeIdx) {+ // must follow a successful sem wait+ if (auto obj = sem_.payload(stripeIdx).try_dequeue()) {+ return std::move(*obj);+ }+ terminate_with<std::logic_error>("bug in task queue");+ }++ static size_t getStripeIdx() { return StripeGetter::get().current(); }++ using Queue = PriorityUMPMCQueueSet<T, /* MayBlock = */ false>;++ const size_t numPriorities_;+ StripedThrottledLifoSem<Queue> sem_;+};++} // namespace folly
@@ -29,7 +29,7 @@ const typename Semaphore::Options& semaphoreOptions = {}) : sem_(semaphoreOptions) {} - BlockingQueueAddResult add(T item) override {+ BlockingQueueAddResult add(T&& item) override { queue_.enqueue(std::move(item)); return sem_.post(); }@@ -46,7 +46,7 @@ return queue_.dequeue(); } - size_t size() override { return queue_.size(); }+ size_t size() override { return sem_.valueGuess(); } private: Semaphore sem_;
@@ -31,8 +31,9 @@ Func&& threadInitializer, Func&& threadFinializer = [] {}) : threadFactory_(std::move(threadFactory)),- threadInitFini_(std::make_shared<ThreadInitFini>(- std::move(threadInitializer), std::move(threadFinializer))) {}+ threadInitFini_(+ std::make_shared<ThreadInitFini>(+ std::move(threadInitializer), std::move(threadFinializer))) {} std::thread newThread(Func&& func) override { return threadFactory_->newThread(
@@ -14,4 +14,4 @@ * limitations under the License. */ -#include <folly/synchronization/EventCount.h>+#include <folly/synchronization/EventCount.h> // @shim
@@ -14,4 +14,4 @@ * limitations under the License. */ -#include <folly/concurrency/container/FlatCombiningPriorityQueue.h>+#include <folly/concurrency/container/FlatCombiningPriorityQueue.h> // @shim
@@ -14,4 +14,4 @@ * limitations under the License. */ -#include <folly/executors/FunctionScheduler.h>+#include <folly/executors/FunctionScheduler.h> // @shim
@@ -1,17 +0,0 @@-/*- * Copyright (c) Meta Platforms, Inc. and affiliates.- *- * Licensed under the Apache License, Version 2.0 (the "License");- * you may not use this file except in compliance with the License.- * You may obtain a copy of the License at- *- * http://www.apache.org/licenses/LICENSE-2.0- *- * Unless required by applicable law or agreed to in writing, software- * distributed under the License is distributed on an "AS IS" BASIS,- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.- * See the License for the specific language governing permissions and- * limitations under the License.- */--#include <folly/testing/TestUtil.h>
@@ -14,4 +14,4 @@ * limitations under the License. */ -#include <folly/executors/ThreadedRepeatingFunctionRunner.h>+#include <folly/executors/ThreadedRepeatingFunctionRunner.h> // @shim
@@ -14,4 +14,4 @@ * limitations under the License. */ -#include <folly/channels/Channel-fwd.h>+#include <folly/channels/Channel-fwd.h> // @shim
@@ -14,4 +14,4 @@ * limitations under the License. */ -#include <folly/channels/Channel-inl.h>+#include <folly/channels/Channel-inl.h> // @shim
@@ -14,4 +14,4 @@ * limitations under the License. */ -#include <folly/channels/Channel.h>+#include <folly/channels/Channel.h> // @shim
@@ -14,4 +14,4 @@ * limitations under the License. */ -#include <folly/channels/ChannelCallbackHandle.h>+#include <folly/channels/ChannelCallbackHandle.h> // @shim
@@ -14,4 +14,4 @@ * limitations under the License. */ -#include <folly/channels/ChannelProcessor-inl.h>+#include <folly/channels/ChannelProcessor-inl.h> // @shim
@@ -14,4 +14,4 @@ * limitations under the License. */ -#include <folly/channels/ChannelProcessor.h>+#include <folly/channels/ChannelProcessor.h> // @shim
@@ -14,4 +14,4 @@ * limitations under the License. */ -#include <folly/channels/ConsumeChannel-inl.h>+#include <folly/channels/ConsumeChannel-inl.h> // @shim
@@ -14,4 +14,4 @@ * limitations under the License. */ -#include <folly/channels/ConsumeChannel.h>+#include <folly/channels/ConsumeChannel.h> // @shim
@@ -14,4 +14,4 @@ * limitations under the License. */ -#include <folly/channels/FanoutChannel-inl.h>+#include <folly/channels/FanoutChannel-inl.h> // @shim
@@ -14,4 +14,4 @@ * limitations under the License. */ -#include <folly/channels/FanoutChannel.h>+#include <folly/channels/FanoutChannel.h> // @shim
@@ -14,4 +14,4 @@ * limitations under the License. */ -#include <folly/channels/FanoutSender-inl.h>+#include <folly/channels/FanoutSender-inl.h> // @shim
@@ -14,4 +14,4 @@ * limitations under the License. */ -#include <folly/channels/FanoutSender.h>+#include <folly/channels/FanoutSender.h> // @shim
@@ -14,4 +14,4 @@ * limitations under the License. */ -#include <folly/channels/MaxConcurrentRateLimiter.h>+#include <folly/channels/MaxConcurrentRateLimiter.h> // @shim
@@ -14,4 +14,4 @@ * limitations under the License. */ -#include <folly/channels/Merge-inl.h>+#include <folly/channels/Merge-inl.h> // @shim
@@ -14,4 +14,4 @@ * limitations under the License. */ -#include <folly/channels/Merge.h>+#include <folly/channels/Merge.h> // @shim
@@ -14,4 +14,4 @@ * limitations under the License. */ -#include <folly/channels/MergeChannel-inl.h>+#include <folly/channels/MergeChannel-inl.h> // @shim
@@ -14,4 +14,4 @@ * limitations under the License. */ -#include <folly/channels/MergeChannel.h>+#include <folly/channels/MergeChannel.h> // @shim
@@ -14,4 +14,4 @@ * limitations under the License. */ -#include <folly/channels/MultiplexChannel-inl.h>+#include <folly/channels/MultiplexChannel-inl.h> // @shim
@@ -14,4 +14,4 @@ * limitations under the License. */ -#include <folly/channels/MultiplexChannel.h>+#include <folly/channels/MultiplexChannel.h> // @shim
@@ -14,4 +14,4 @@ * limitations under the License. */ -#include <folly/channels/OnClosedException.h>+#include <folly/channels/OnClosedException.h> // @shim
@@ -14,4 +14,4 @@ * limitations under the License. */ -#include <folly/channels/Producer-inl.h>+#include <folly/channels/Producer-inl.h> // @shim
@@ -14,4 +14,4 @@ * limitations under the License. */ -#include <folly/channels/Producer.h>+#include <folly/channels/Producer.h> // @shim
@@ -14,4 +14,4 @@ * limitations under the License. */ -#include <folly/channels/ProxyChannel-inl.h>+#include <folly/channels/ProxyChannel-inl.h> // @shim
@@ -14,4 +14,4 @@ * limitations under the License. */ -#include <folly/channels/ProxyChannel.h>+#include <folly/channels/ProxyChannel.h> // @shim
@@ -14,4 +14,4 @@ * limitations under the License. */ -#include <folly/channels/RateLimiter.h>+#include <folly/channels/RateLimiter.h> // @shim
@@ -14,4 +14,4 @@ * limitations under the License. */ -#include <folly/channels/Transform-inl.h>+#include <folly/channels/Transform-inl.h> // @shim
@@ -14,4 +14,4 @@ * limitations under the License. */ -#include <folly/channels/Transform.h>+#include <folly/channels/Transform.h> // @shim
@@ -14,4 +14,4 @@ * limitations under the License. */ -#include <folly/channels/detail/AtomicQueue.h>+#include <folly/channels/detail/AtomicQueue.h> // @shim
@@ -14,4 +14,4 @@ * limitations under the License. */ -#include <folly/channels/detail/ChannelBridge.h>+#include <folly/channels/detail/ChannelBridge.h> // @shim
@@ -14,4 +14,4 @@ * limitations under the License. */ -#include <folly/channels/detail/FunctionTraits.h>+#include <folly/channels/detail/FunctionTraits.h> // @shim
@@ -14,4 +14,4 @@ * limitations under the License. */ -#include <folly/channels/detail/IntrusivePtr.h>+#include <folly/channels/detail/IntrusivePtr.h> // @shim
@@ -14,4 +14,4 @@ * limitations under the License. */ -#include <folly/channels/detail/MultiplexerTraits.h>+#include <folly/channels/detail/MultiplexerTraits.h> // @shim
@@ -14,4 +14,4 @@ * limitations under the License. */ -#include <folly/channels/detail/PointerVariant.h>+#include <folly/channels/detail/PointerVariant.h> // @shim
@@ -14,4 +14,4 @@ * limitations under the License. */ -#include <folly/channels/detail/Utility.h>+#include <folly/channels/detail/Utility.h> // @shim
@@ -14,4 +14,4 @@ * limitations under the License. */ -#include <folly/coro/AsyncGenerator.h>+#include <folly/coro/AsyncGenerator.h> // @shim
@@ -14,4 +14,4 @@ * limitations under the License. */ -#include <folly/coro/AsyncPipe.h>+#include <folly/coro/AsyncPipe.h> // @shim
@@ -14,4 +14,4 @@ * limitations under the License. */ -#include <folly/coro/AsyncScope.h>+#include <folly/coro/AsyncScope.h> // @shim
@@ -14,4 +14,4 @@ * limitations under the License. */ -#include <folly/coro/AsyncStack.h>+#include <folly/coro/AsyncStack.h> // @shim
@@ -14,4 +14,4 @@ * limitations under the License. */ -#include <folly/coro/AutoCleanup-fwd.h>+#include <folly/coro/AutoCleanup-fwd.h> // @shim
@@ -14,4 +14,4 @@ * limitations under the License. */ -#include <folly/coro/AutoCleanup.h>+#include <folly/coro/AutoCleanup.h> // @shim
@@ -14,4 +14,4 @@ * limitations under the License. */ -#include <folly/coro/Baton.h>+#include <folly/coro/Baton.h> // @shim
@@ -14,4 +14,4 @@ * limitations under the License. */ -#include <folly/coro/BlockingWait.h>+#include <folly/coro/BlockingWait.h> // @shim
@@ -14,4 +14,4 @@ * limitations under the License. */ -#include <folly/coro/BoundedQueue.h>+#include <folly/coro/BoundedQueue.h> // @shim
@@ -14,4 +14,4 @@ * limitations under the License. */ -#include <folly/coro/Cleanup.h>+#include <folly/coro/Cleanup.h> // @shim
@@ -14,4 +14,4 @@ * limitations under the License. */ -#include <folly/coro/Collect-inl.h>+#include <folly/coro/Collect-inl.h> // @shim
@@ -14,4 +14,4 @@ * limitations under the License. */ -#include <folly/coro/Collect.h>+#include <folly/coro/Collect.h> // @shim
@@ -14,4 +14,4 @@ * limitations under the License. */ -#include <folly/coro/Concat-inl.h>+#include <folly/coro/Concat-inl.h> // @shim
@@ -14,4 +14,4 @@ * limitations under the License. */ -#include <folly/coro/Concat.h>+#include <folly/coro/Concat.h> // @shim
@@ -14,4 +14,4 @@ * limitations under the License. */ -#include <folly/coro/Coroutine.h>+#include <folly/coro/Coroutine.h> // @shim
@@ -14,4 +14,4 @@ * limitations under the License. */ -#include <folly/coro/CurrentExecutor.h>+#include <folly/coro/CurrentExecutor.h> // @shim
@@ -14,4 +14,4 @@ * limitations under the License. */ -#include <folly/coro/DetachOnCancel.h>+#include <folly/coro/DetachOnCancel.h> // @shim
@@ -14,4 +14,4 @@ * limitations under the License. */ -#include <folly/coro/Filter-inl.h>+#include <folly/coro/Filter-inl.h> // @shim
@@ -14,4 +14,4 @@ * limitations under the License. */ -#include <folly/coro/Filter.h>+#include <folly/coro/Filter.h> // @shim
@@ -14,4 +14,4 @@ * limitations under the License. */ -#include <folly/coro/FutureUtil.h>+#include <folly/coro/FutureUtil.h> // @shim
@@ -14,4 +14,4 @@ * limitations under the License. */ -#include <folly/coro/Generator.h>+#include <folly/coro/Generator.h> // @shim
@@ -14,4 +14,4 @@ * limitations under the License. */ -#include <folly/coro/GmockHelpers.h>+#include <folly/coro/GmockHelpers.h> // @shim
@@ -14,4 +14,4 @@ * limitations under the License. */ -#include <folly/coro/GtestHelpers.h>+#include <folly/coro/GtestHelpers.h> // @shim
@@ -14,4 +14,4 @@ * limitations under the License. */ -#include <folly/coro/Invoke.h>+#include <folly/coro/Invoke.h> // @shim
@@ -14,4 +14,4 @@ * limitations under the License. */ -#include <folly/coro/Merge-inl.h>+#include <folly/coro/Merge-inl.h> // @shim
@@ -14,4 +14,4 @@ * limitations under the License. */ -#include <folly/coro/Merge.h>+#include <folly/coro/Merge.h> // @shim
@@ -14,4 +14,4 @@ * limitations under the License. */ -#include <folly/coro/Mutex.h>+#include <folly/coro/Mutex.h> // @shim
@@ -14,4 +14,4 @@ * limitations under the License. */ -#include <folly/coro/Promise.h>+#include <folly/coro/Promise.h> // @shim
@@ -14,4 +14,4 @@ * limitations under the License. */ -#include <folly/coro/Result.h>+#include <folly/coro/Result.h> // @shim
@@ -14,4 +14,4 @@ * limitations under the License. */ -#include <folly/coro/Retry.h>+#include <folly/coro/Retry.h> // @shim
@@ -14,4 +14,4 @@ * limitations under the License. */ -#include <folly/coro/RustAdaptors.h>+#include <folly/coro/RustAdaptors.h> // @shim
@@ -14,4 +14,4 @@ * limitations under the License. */ -#include <folly/coro/ScopeExit.h>+#include <folly/coro/ScopeExit.h> // @shim
@@ -14,4 +14,4 @@ * limitations under the License. */ -#include <folly/coro/SharedLock.h>+#include <folly/coro/SharedLock.h> // @shim
@@ -14,4 +14,4 @@ * limitations under the License. */ -#include <folly/coro/SharedMutex.h>+#include <folly/coro/SharedMutex.h> // @shim
@@ -14,4 +14,4 @@ * limitations under the License. */ -#include <folly/coro/SharedPromise.h>+#include <folly/coro/SharedPromise.h> // @shim
@@ -14,4 +14,4 @@ * limitations under the License. */ -#include <folly/coro/Sleep-inl.h>+#include <folly/coro/Sleep-inl.h> // @shim
@@ -14,4 +14,4 @@ * limitations under the License. */ -#include <folly/coro/Sleep.h>+#include <folly/coro/Sleep.h> // @shim
@@ -14,4 +14,4 @@ * limitations under the License. */ -#include <folly/coro/SmallUnboundedQueue.h>+#include <folly/coro/SmallUnboundedQueue.h> // @shim
@@ -14,4 +14,4 @@ * limitations under the License. */ -#include <folly/coro/Task.h>+#include <folly/coro/Task.h> // @shim
@@ -14,4 +14,4 @@ * limitations under the License. */ -#include <folly/coro/TimedWait.h>+#include <folly/coro/TimedWait.h> // @shim
@@ -14,4 +14,4 @@ * limitations under the License. */ -#include <folly/coro/Timeout-inl.h>+#include <folly/coro/Timeout-inl.h> // @shim
@@ -14,4 +14,4 @@ * limitations under the License. */ -#include <folly/coro/Timeout.h>+#include <folly/coro/Timeout.h> // @shim
@@ -14,4 +14,4 @@ * limitations under the License. */ -#include <folly/coro/Traits.h>+#include <folly/coro/Traits.h> // @shim
@@ -14,4 +14,4 @@ * limitations under the License. */ -#include <folly/coro/Transform-inl.h>+#include <folly/coro/Transform-inl.h> // @shim
@@ -14,4 +14,4 @@ * limitations under the License. */ -#include <folly/coro/Transform.h>+#include <folly/coro/Transform.h> // @shim
@@ -14,4 +14,4 @@ * limitations under the License. */ -#include <folly/coro/UnboundedQueue.h>+#include <folly/coro/UnboundedQueue.h> // @shim
@@ -14,4 +14,4 @@ * limitations under the License. */ -#include <folly/coro/ViaIfAsync.h>+#include <folly/coro/ViaIfAsync.h> // @shim
@@ -14,4 +14,4 @@ * limitations under the License. */ -#include <folly/coro/WithAsyncStack.h>+#include <folly/coro/WithAsyncStack.h> // @shim
@@ -14,4 +14,4 @@ * limitations under the License. */ -#include <folly/coro/WithCancellation.h>+#include <folly/coro/WithCancellation.h> // @shim
@@ -14,4 +14,4 @@ * limitations under the License. */ -#include <folly/coro/detail/Barrier.h>+#include <folly/coro/detail/Barrier.h> // @shim
@@ -14,4 +14,4 @@ * limitations under the License. */ -#include <folly/coro/detail/BarrierTask.h>+#include <folly/coro/detail/BarrierTask.h> // @shim
@@ -14,4 +14,4 @@ * limitations under the License. */ -#include <folly/coro/detail/CurrentAsyncFrame.h>+#include <folly/coro/detail/CurrentAsyncFrame.h> // @shim
@@ -14,4 +14,4 @@ * limitations under the License. */ -#include <folly/coro/detail/Helpers.h>+#include <folly/coro/detail/Helpers.h> // @shim
@@ -14,4 +14,4 @@ * limitations under the License. */ -#include <folly/coro/detail/InlineTask.h>+#include <folly/coro/detail/InlineTask.h> // @shim
@@ -14,4 +14,4 @@ * limitations under the License. */ -#include <folly/coro/detail/Malloc.h>+#include <folly/coro/detail/Malloc.h> // @shim
@@ -14,4 +14,4 @@ * limitations under the License. */ -#include <folly/coro/detail/ManualLifetime.h>+#include <folly/coro/detail/ManualLifetime.h> // @shim
@@ -14,4 +14,4 @@ * limitations under the License. */ -#include <folly/coro/detail/Traits.h>+#include <folly/coro/detail/Traits.h> // @shim
@@ -16,4 +16,4 @@ #pragma once -#include <folly/crypto/Blake2xb.h>+#include <folly/crypto/Blake2xb.h> // @shim
@@ -16,4 +16,4 @@ #pragma once -#include <folly/crypto/LtHash.h>+#include <folly/crypto/LtHash.h> // @shim
@@ -14,4 +14,4 @@ * limitations under the License. */ -#include <folly/debugging/exception_tracer/ExceptionAbi.h>+#include <folly/debugging/exception_tracer/ExceptionAbi.h> // @shim
@@ -14,4 +14,4 @@ * limitations under the License. */ -#include <folly/debugging/exception_tracer/ExceptionCounterLib.h>+#include <folly/debugging/exception_tracer/ExceptionCounterLib.h> // @shim
@@ -14,4 +14,4 @@ * limitations under the License. */ -#include <folly/debugging/exception_tracer/ExceptionTracer.h>+#include <folly/debugging/exception_tracer/ExceptionTracer.h> // @shim
@@ -14,4 +14,4 @@ * limitations under the License. */ -#include <folly/debugging/exception_tracer/ExceptionTracerLib.h>+#include <folly/debugging/exception_tracer/ExceptionTracerLib.h> // @shim
@@ -14,4 +14,4 @@ * limitations under the License. */ -#include <folly/debugging/exception_tracer/SmartExceptionTracer.h>+#include <folly/debugging/exception_tracer/SmartExceptionTracer.h> // @shim
@@ -14,4 +14,4 @@ * limitations under the License. */ -#include <folly/debugging/exception_tracer/SmartExceptionTracerSingleton.h>+#include <folly/debugging/exception_tracer/SmartExceptionTracerSingleton.h> // @shim
@@ -14,4 +14,4 @@ * limitations under the License. */ -#include <folly/debugging/exception_tracer/StackTrace.h>+#include <folly/debugging/exception_tracer/StackTrace.h> // @shim
@@ -14,4 +14,4 @@ * limitations under the License. */ -#include <folly/synchronization/FlatCombining.h>+#include <folly/synchronization/FlatCombining.h> // @shim
@@ -14,4 +14,4 @@ * limitations under the License. */ -#include <folly/io/async/AsyncBase.h>+#include <folly/io/async/AsyncBase.h> // @shim
@@ -14,4 +14,4 @@ * limitations under the License. */ -#include <folly/io/async/AsyncIO.h>+#include <folly/io/async/AsyncIO.h> // @shim
@@ -14,4 +14,4 @@ * limitations under the License. */ -#include <folly/io/async/AsyncIoUringSocket.h>+#include <folly/io/async/AsyncIoUringSocket.h> // @shim
@@ -14,4 +14,4 @@ * limitations under the License. */ -#include <folly/io/async/AsyncIoUringSocketFactory.h>+#include <folly/io/async/AsyncIoUringSocketFactory.h> // @shim
@@ -14,4 +14,4 @@ * limitations under the License. */ -#include <folly/io/async/Epoll.h>+#include <folly/io/async/Epoll.h> // @shim
@@ -14,4 +14,4 @@ * limitations under the License. */ -#include <folly/io/async/EpollBackend.h>+#include <folly/io/async/EpollBackend.h> // @shim
@@ -14,4 +14,4 @@ * limitations under the License. */ -#include <folly/io/async/EventBasePoller.h>+#include <folly/io/async/EventBasePoller.h> // @shim
@@ -14,4 +14,4 @@ * limitations under the License. */ -#include <folly/io/FsUtil.h>+#include <folly/io/FsUtil.h> // @shim
@@ -14,4 +14,4 @@ * limitations under the License. */ -#include <folly/io/HugePages.h>+#include <folly/io/HugePages.h> // @shim
@@ -14,4 +14,4 @@ * limitations under the License. */ -#include <folly/io/async/IoUring.h>+#include <folly/io/async/IoUring.h> // @shim
@@ -14,4 +14,4 @@ * limitations under the License. */ -#include <folly/io/async/IoUringBackend.h>+#include <folly/io/async/IoUringBackend.h> // @shim
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff