Skip to content

Commit

Permalink
Implement NativePromise
Browse files Browse the repository at this point in the history
https://bugs.webkit.org/show_bug.cgi?id=254502
rdar://107546076

Reviewed by Youenn Fablet.

This is a WTF C++ native JS's like Promise, inspired by Gecko's MozPromise object

There are some significant differences from the gecko's implemtation,
both from a modus of operations and syntax usage in order to better fit
with WebKit, in particular:
- NativePromise can have a resolveType of `void`
- NativePromise takes a SerialFunctionDispatcher with ref/deref methods and
  is a template argument.
- There's typically no need to specify the return types of the resolve/reject callbacks.
  `auto` should work in most cases.

About NativePromise:

A promise manages an asynchronous request that may or may not be able to be fulfilled immediately.
When an API returns a promise, the consumer may attach callbacks to be invoked (asynchronously, on a specified thread)
when the request is either completed (resolved) or cannot be completed (rejected).

A NativePromise object is thread safe, and may be ->then()ed on any thread.
The then() call accepts either a resolve and reject callback, while whenSettled() accepts a resolveOrReject one.

NativePromise::then() and NativePromise::whenSettled() returns a NativePromise::Request object. This request can be either:
1- Converted back to a NativePromise which will be resolved or rejected once the resolve/reject callbacks are run.
  This new NativePromise can be then()ed again to chain multiple operations.
2- Be tracked using a NativePromiseRequest: this allows the caller to cancel the delivery of the resolve/reject result if it has not already occurred.
  (call to NativePromiseRequest::disconnect() must be done on the target thread to avoid thread safety issues).

When IsExclusive is true:
- The NativePromise performs run-time assertions that there is at most one call to either then(...) or chainTo(...).
- Move semantics are used when passing arguments
- The resolved or rejected object will be deleted on the target thread.
- The ResolveValueType and RejectValueType must have a move constructor if IsExclusive is true. Compilation will fail otherwise.
Otherwise:
- values are passed to the resolve/reject callbacks through either const references or pointers.
- the resolve or reject object will be deleted on the last SerialFunctionDispatcher that got used.

A typical workflow would be as follow:
If the work is to be done immediately:
From the producer side:
- Do the work
- return a resolved or rejected promise via NativePromise::createAndResolve or NativePromise::createAndReject
From the consumer side:
- call the method returning a promise
- then()/whenSettled() on the promise to set the actions to run once the promise has settled.

If the work is to be done at a later stage:
From the producer side:
- Allocate a NativePromise::Producer (via NativePromise::Producer::create() and return it to the consumer has a Ref<NativePromise>
- Do the work
- Once the work has been completed, either resolve or reject the NativePromise::Producer object.
From the consumer side:
- call the method returning a promise
- then() on the promise to set the actions to run once the promise has settled.

In either case (immediate or later resolution) using a NativePromiseRequest:
- track the promise
- cancel the delivery of the resolve/reject result and prevent callbacks to be run.

By disconnecting the NativePromiseRequest (via NativePromiseRequest::disconnect(), the then() callbacks will not be run.

Example:
```
        // You can mix & match promise types and chain them together.
        using MyPromise = NativePromise<int, int, true>;
        GenericPromise::Producer p(__func__);
        using MyPromise = NativePromise<int, int, true>;
        p->whenSettled(queue, __func__, [] (GenericPromise::Result result) {
            return MyPromise::createAndResolve(1, __func__);
        })->whenSettled(queue, __func__, [queue] (MyPromise::Result result) {
            static_assert(std::is_same_v<MyPromise::Result::value_type, int>, "The type received is the same as the last promise returned");
            EXPECT_TRUE(result.has_value());
            EXPECT_EQ(result.value(), 1);
            queue->beginShutdown();
        });
        p.resolve(__func__);
```

API tests added (re-used from Gecko's source code and adapted for NativePromise)

Canonical link: https://commits.webkit.org/268120@main
  • Loading branch information
jyavenard committed Sep 19, 2023
1 parent c87c377 commit a53ad48
Show file tree
Hide file tree
Showing 11 changed files with 2,270 additions and 0 deletions.
12 changes: 12 additions & 0 deletions Source/WTF/WTF.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,10 @@
46E93049271F1205005BA6E5 /* SafeStrerror.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 46E43647271F10AA00C88C90 /* SafeStrerror.cpp */; };
50DE35F5215BB01500B979C7 /* ExternalStringImpl.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 50DE35F3215BB01500B979C7 /* ExternalStringImpl.cpp */; };
515F794E1CFC9F4A00CCED93 /* CrossThreadCopier.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 515F794B1CFC9F4A00CCED93 /* CrossThreadCopier.cpp */; };
5164042D2AB1D46B0042B1C3 /* NativePromise.h in Headers */ = {isa = PBXBuildFile; fileRef = 5164042C2AB1D46B0042B1C3 /* NativePromise.h */; settings = {ATTRIBUTES = (Private, ); }; };
5164042F2AB1D4DD0042B1C3 /* TypeTraits.h in Headers */ = {isa = PBXBuildFile; fileRef = 5164042E2AB1D4DD0042B1C3 /* TypeTraits.h */; settings = {ATTRIBUTES = (Private, ); }; };
517F82D71FD22F3000DA3DEA /* CrossThreadTaskHandler.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 517F82D51FD22F2F00DA3DEA /* CrossThreadTaskHandler.cpp */; };
51B07F542AB8797300E14EE9 /* NativePromise.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 51B07F532AB8797300E14EE9 /* NativePromise.cpp */; };
51F1752B1F3D486000C74950 /* PersistentCoders.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 51F175261F3D486000C74950 /* PersistentCoders.cpp */; };
51F1752C1F3D486000C74950 /* PersistentDecoder.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 51F175271F3D486000C74950 /* PersistentDecoder.cpp */; };
51F1752D1F3D486000C74950 /* PersistentEncoder.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 51F175291F3D486000C74950 /* PersistentEncoder.cpp */; };
Expand Down Expand Up @@ -1146,10 +1149,13 @@
515F794C1CFC9F4A00CCED93 /* CrossThreadCopier.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CrossThreadCopier.h; sourceTree = "<group>"; };
515F794D1CFC9F4A00CCED93 /* CrossThreadTask.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CrossThreadTask.h; sourceTree = "<group>"; };
515F79551CFD3A6900CCED93 /* CrossThreadQueue.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CrossThreadQueue.h; sourceTree = "<group>"; };
5164042C2AB1D46B0042B1C3 /* NativePromise.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = NativePromise.h; sourceTree = "<group>"; };
5164042E2AB1D4DD0042B1C3 /* TypeTraits.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TypeTraits.h; sourceTree = "<group>"; };
517A53571F5734B700DCDC0A /* Identified.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Identified.h; sourceTree = "<group>"; };
517F82D51FD22F2F00DA3DEA /* CrossThreadTaskHandler.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = CrossThreadTaskHandler.cpp; sourceTree = "<group>"; };
517F82D61FD22F2F00DA3DEA /* CrossThreadTaskHandler.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CrossThreadTaskHandler.h; sourceTree = "<group>"; };
5182C22C1F2BC7E60059BA7C /* InstanceCounted.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = InstanceCounted.h; sourceTree = "<group>"; };
51B07F532AB8797300E14EE9 /* NativePromise.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = NativePromise.cpp; sourceTree = "<group>"; };
51F175251F3D486000C74950 /* PersistentCoders.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PersistentCoders.h; sourceTree = "<group>"; };
51F175261F3D486000C74950 /* PersistentCoders.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = PersistentCoders.cpp; sourceTree = "<group>"; };
51F175271F3D486000C74950 /* PersistentDecoder.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = PersistentDecoder.cpp; sourceTree = "<group>"; };
Expand Down Expand Up @@ -2191,6 +2197,8 @@
0F66B2831DC97BAB004A1D3F /* MonotonicTime.h */,
FE8225301B2A1E5B00BA68FD /* NakedPtr.h */,
E32A207323C5902D0034A092 /* NakedRef.h */,
51B07F532AB8797300E14EE9 /* NativePromise.cpp */,
5164042C2AB1D46B0042B1C3 /* NativePromise.h */,
0F5BF1651F2317830029D91D /* NaturalLoops.h */,
1A3F6BE6174ADA2100B2EEA7 /* NeverDestroyed.h */,
0F0D85B317234CB100338210 /* NoLock.h */,
Expand Down Expand Up @@ -2370,6 +2378,7 @@
2D1F2F462498F73300C63A83 /* TranslatedProcess.h */,
149EF16216BBFE0D000A4331 /* TriState.h */,
83FBA93119DF459700F30ADB /* TypeCasts.h */,
5164042E2AB1D4DD0042B1C3 /* TypeTraits.h */,
E360C7642127B85B00C90F0E /* UnalignedAccess.h */,
E360C7652127B85C00C90F0E /* Unexpected.h */,
A8A4735C151A825B004123FF /* UnionFind.h */,
Expand Down Expand Up @@ -3159,6 +3168,7 @@
DD3DC96827A4BF8E007E5B61 /* MonotonicTime.h in Headers */,
DD3DC96227A4BF8E007E5B61 /* NakedPtr.h in Headers */,
DD3DC98A27A4BF8E007E5B61 /* NakedRef.h in Headers */,
5164042D2AB1D46B0042B1C3 /* NativePromise.h in Headers */,
DD3DC8D827A4BF8E007E5B61 /* NaturalLoops.h in Headers */,
DD3DC8AC27A4BF8E007E5B61 /* NeverDestroyed.h in Headers */,
DD3DC98C27A4BF8E007E5B61 /* NoLock.h in Headers */,
Expand Down Expand Up @@ -3353,6 +3363,7 @@
DD3DC87427A4BF8E007E5B61 /* TypeCasts.h in Headers */,
DDF3070C27C086CC006A526F /* TypeCastsCF.h in Headers */,
DDF306FD27C086CC006A526F /* TypeCastsCocoa.h in Headers */,
5164042F2AB1D4DD0042B1C3 /* TypeTraits.h in Headers */,
DD3DC8C927A4BF8E007E5B61 /* UnalignedAccess.h in Headers */,
DD3DC86A27A4BF8E007E5B61 /* Unexpected.h in Headers */,
DD28468829248DDA0009A61D /* UnifiedWebPreferences.yaml in Headers */,
Expand Down Expand Up @@ -3821,6 +3832,7 @@
AD89B6BA1E64150F0090707F /* MemoryPressureHandlerCocoa.mm in Sources */,
A8A473EC151A825B004123FF /* MetaAllocator.cpp in Sources */,
0F66B28C1DC97BAB004A1D3F /* MonotonicTime.cpp in Sources */,
51B07F542AB8797300E14EE9 /* NativePromise.cpp in Sources */,
5CC0EE8A2162BC2200A1A842 /* NSURLExtras.mm in Sources */,
A8A473F4151A825B004123FF /* NumberOfCores.cpp in Sources */,
8348BA0E21FBC0D500FD3054 /* ObjectIdentifier.cpp in Sources */,
Expand Down
3 changes: 3 additions & 0 deletions Source/WTF/wtf/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,7 @@ set(WTF_PUBLIC_HEADERS
MonotonicTime.h
NakedPtr.h
NakedRef.h
NativePromise.h
NaturalLoops.h
NeverDestroyed.h
NoLock.h
Expand Down Expand Up @@ -307,6 +308,7 @@ set(WTF_PUBLIC_HEADERS
TranslatedProcess.h
TriState.h
TypeCasts.h
TypeTraits.h
URL.h
URLHelpers.h
URLHash.h
Expand Down Expand Up @@ -476,6 +478,7 @@ set(WTF_SOURCES
MemoryPressureHandler.cpp
MetaAllocator.cpp
MonotonicTime.cpp
NativePromise.cpp
NumberOfCores.cpp
OSRandomSource.cpp
ObjectIdentifier.cpp
Expand Down
1 change: 1 addition & 0 deletions Source/WTF/wtf/Logging.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ DEFINE_LOG_CHANNEL_WITH_DETAILS(Process, logChannelStateOn, logLevelErr
DEFINE_LOG_CHANNEL_WITH_DETAILS(Threading, logChannelStateOn, logLevelError, LOG_CHANNEL_WEBKIT_SUBSYSTEM);
DEFINE_LOG_CHANNEL(MemoryPressure, LOG_CHANNEL_WEBKIT_SUBSYSTEM);
DEFINE_LOG_CHANNEL(SuspendableWorkQueue, LOG_CHANNEL_WEBKIT_SUBSYSTEM);
DEFINE_LOG_CHANNEL(NativePromise, LOG_CHANNEL_WEBKIT_SUBSYSTEM);

#endif // !LOG_DISABLED || !RELEASE_LOG_DISABLED

Expand Down
1 change: 1 addition & 0 deletions Source/WTF/wtf/Logging.h
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ namespace WTF {
M(Threading) \
M(MemoryPressure) \
M(SuspendableWorkQueue) \
M(NativePromise) \

#undef DECLARE_LOG_CHANNEL
#define DECLARE_LOG_CHANNEL(name) \
Expand Down
42 changes: 42 additions & 0 deletions Source/WTF/wtf/NativePromise.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
/*
* Copyright (C) 2023 Apple Inc. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
* OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/

#include "config.h"
#include <wtf/NativePromise.h>

#if !LOG_DISABLED || !RELEASE_LOG_DISABLED

#include <wtf/Logging.h>

namespace WTF {

WTFLogChannel* NativePromiseBase::logChannel()
{
return &WTFLogNativePromise;
}

} // namespace WTF

#endif
Loading

0 comments on commit a53ad48

Please sign in to comment.