| // Copyright 2021 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #ifndef CONTENT_BROWSER_INTEREST_GROUP_AUCTION_PROCESS_MANAGER_H_ |
| #define CONTENT_BROWSER_INTEREST_GROUP_AUCTION_PROCESS_MANAGER_H_ |
| |
| #include <cstddef> |
| #include <list> |
| #include <map> |
| #include <memory> |
| #include <optional> |
| #include <set> |
| #include <string> |
| |
| #include "base/functional/callback.h" |
| #include "base/memory/raw_ptr.h" |
| #include "base/memory/ref_counted.h" |
| #include "base/memory/scoped_refptr.h" |
| #include "base/memory/weak_ptr.h" |
| #include "base/process/process_handle.h" |
| #include "base/timer/timer.h" |
| #include "content/common/content_export.h" |
| #include "content/public/browser/render_process_host_observer.h" |
| #include "content/services/auction_worklet/public/mojom/auction_worklet_service.mojom.h" |
| #include "mojo/public/cpp/bindings/pending_receiver.h" |
| #include "mojo/public/cpp/bindings/remote.h" |
| #include "url/origin.h" |
| |
| namespace base { |
| class Process; |
| } // namespace base |
| |
| namespace content { |
| |
| class RenderProcessHost; |
| class SiteInstance; |
| class TrustedSignalsCacheImpl; |
| |
| // Base class of per-StoragePartition manager of auction bidder and seller |
| // worklet processes. This provides limiting and sharing of worker processes. |
| // |
| // AuctionProcessManager managers two types of processes, idle processes, and |
| // non-idle processes. |
| // |
| // Idle processes are owned directly by AuctionProcessManager::idle_processes_, |
| // and have no associated ProcessHandle -- they have a WorkletProcess only. On |
| // process crash or idle timeout, they tell the AuctionProcessManager to destroy |
| // them. |
| // |
| // Non-idle processes have been handed out to one or more live ProcessHandles, |
| // and are tracked in one of the AuctionProcessManager's ProcessMap with |
| // raw pointers. When the last ProcessHandle releases a reference to the |
| // WorkletProcess, it's destroyed, and informs the AuctionProcessManager to |
| // remove it from the map. On process crash, it may also be removed from the |
| // map, to prevent reuse, even though consumers may still own references to it. |
| class CONTENT_EXPORT AuctionProcessManager { |
| public: |
| // The maximum number of bidder processes. Once this number is reached, no |
| // processes will be created for bidder worklets, though new bidder worklet |
| // requests can receive pre-existing processes. |
| static const size_t kMaxBidderProcesses; |
| |
| // The maximum number of seller processes. Once this number is reached, no |
| // processes will be created for seller worklets, though new seller worklet |
| // requests can receive pre-existing processes. Distinct from |
| // kMaxBidderProcesses because sellers behave a bit differently - they're |
| // alive for the length of the auction. Also, if a putative entire shared |
| // process limit were consumed by seller worklets, no more auctions could run, |
| // since bidder worklets couldn't load to make bids. |
| static const size_t kMaxSellerProcesses; |
| |
| // The two worklet types. Sellers and bidders never share processes, primarily |
| // to make accounting simpler. They also currently issue requests with |
| // different NIKs, so safest to keep them separate, anyways. |
| enum class WorkletType { |
| kBidder, |
| kSeller, |
| }; |
| |
| // Outcome of RequestWorkletService. |
| // These values are persisted to logs. Entries should not be renumbered and |
| // numeric values should never be reused. |
| enum class RequestWorkletServiceOutcome { |
| kHitProcessLimit = 0, |
| kUsedSharedProcess = 1, |
| kUsedExistingDedicatedProcess = 2, |
| kCreatedNewDedicatedProcess = 3, |
| kUsedIdleProcess = 4, |
| kMaxValue = kUsedIdleProcess |
| }; |
| |
| class ProcessHandle; |
| |
| // Refcounted class that creates / holds Mojo Remote for an |
| // AuctionWorkletService. Only public so it can be used by ProcessHandle and |
| // by test classes. |
| class CONTENT_EXPORT WorkletProcess : public base::RefCounted<WorkletProcess>, |
| public RenderProcessHostObserver { |
| public: |
| // The Mojo pipe and related data passed in when attaching a process to a |
| // WorkletProcess. To make mocking easier, and given the laundry list of |
| // parameters, WorkletProcesses are created without a service pipe. The |
| // AuctionWorkletProcessManager subclass then immediately passes in a |
| // ProcessContext to the WorkletProcess. |
| struct CONTENT_EXPORT ProcessContext { |
| explicit ProcessContext( |
| mojo::PendingRemote<auction_worklet::mojom::AuctionWorkletService> |
| service, |
| RenderProcessHost* render_process_host = nullptr); |
| ProcessContext(ProcessContext&&); |
| ~ProcessContext(); |
| |
| mojo::PendingRemote<auction_worklet::mojom::AuctionWorkletService> |
| service; |
| |
| // May only be non-null when the WorkletProcess was created with a |
| // non-null ServiceInstance. |
| raw_ptr<RenderProcessHost> render_process_host; |
| }; |
| |
| // `is_idle` indicates whether the process will be immediately used. If not, |
| // a timer is started, and if it triggers before ActivateAndBindIfUnbound() |
| // is invoked, the AuctionProcessManager is told to delete process. |
| // |
| // `is_bound_to_origin` indicates if the process may only be used for the |
| // specified worklet_type and origin, or if it may be used for other values |
| // if needed. Only newly created idle processes may not be bound to an |
| // origin. |
| WorkletProcess( |
| AuctionProcessManager* auction_process_manager, |
| scoped_refptr<SiteInstance> site_instance, |
| WorkletType worklet_type, |
| const url::Origin& origin, |
| bool uses_shared_process, |
| bool is_idle, |
| bool is_bound_to_origin); |
| |
| auction_worklet::mojom::AuctionWorkletService* GetService(); |
| |
| WorkletType worklet_type() const { return worklet_type_; } |
| |
| const url::Origin& origin() const { return origin_; } |
| |
| bool is_bound_to_origin() const { return is_bound_to_origin_; } |
| |
| RenderProcessHost* render_process_host() const { |
| return render_process_host_; |
| } |
| |
| std::optional<base::ProcessId> GetPid( |
| base::OnceCallback<void(base::ProcessId)> callback); |
| |
| bool HasPid() const; |
| |
| void OnLaunchedWithProcess(const base::Process& process); |
| |
| // Sets the worklet type and origin to these values, without |
| // binding this process to the values. |
| // This function may only be called on an unbound process |
| // (i.e. `is_bound_to_origin` is false). |
| void ReassignWorkletTypeAndOrigin(WorkletType worklet_type, |
| const url::Origin& origin); |
| |
| // Sets this process non-idle. Binds the worklet type and origin to these |
| // values if this process was not already bound to an origin and type. This |
| // function should only be called on an idle process. |
| void ActivateAndBindIfUnbound(WorkletType worklet_type, |
| const url::Origin& origin); |
| |
| SiteInstance* site_instance() { return site_instance_.get(); } |
| |
| // Returns a weak pointer so that tests can hold onto a pointer to the |
| // WorkletProcess without affecting lifetimes. |
| base::WeakPtr<WorkletProcess> GetWeakPtrForTesting() { |
| return weak_ptr_factory_.GetWeakPtr(); |
| } |
| |
| private: |
| friend class base::RefCounted<WorkletProcess>; |
| friend class DedicatedAuctionProcessManager; |
| friend class InRendererAuctionProcessManager; |
| |
| // Used to set the Mojo service. Called immediately after construction. |
| void SetService(ProcessContext service_context); |
| |
| // From RenderProcessHostObserver: |
| void RenderProcessReady(RenderProcessHost* host) override; |
| |
| void RenderProcessHostDestroyed(RenderProcessHost* host) override; |
| |
| void RemoveFromProcessManager(bool on_destruction); |
| |
| ~WorkletProcess() override; |
| |
| // Must be called when setting `is_bound_to_origin_` to true, if |
| // SetService() has already been invoked. At that point, the origin the |
| // service is bound to will not change, so this method can pass an |
| // origin-bound TrustedSignalsCache Mojo pipe to `service_`, if the cache is |
| // enabled. |
| void OnBoundToOrigin(); |
| |
| raw_ptr<RenderProcessHost> render_process_host_; |
| |
| // SiteInstance representing the worklet. Used only by |
| // InRendererAuctionProcessManager. |
| scoped_refptr<SiteInstance> site_instance_; |
| |
| WorkletType worklet_type_; |
| url::Origin origin_; |
| const base::TimeTicks start_time_; |
| bool uses_shared_process_; |
| |
| std::optional<base::ProcessId> pid_; |
| std::vector<base::OnceCallback<void(base::ProcessId)>> waiting_for_pid_; |
| |
| // nulled out once OnWorkletProcessUnusable() called. |
| raw_ptr<AuctionProcessManager> auction_process_manager_; |
| |
| mojo::Remote<auction_worklet::mojom::AuctionWorkletService> service_; |
| |
| // Whether the process is idle or not. If idle, it is owned directly by the |
| // AuctionProcessManager. If not, it is held by one or more |
| // ProcessHandles as scoped_refptrs. |
| bool is_idle_; |
| |
| // Whether the origin and worklet type are bound to this process. If this |
| // worklet has ever been used, or if it's a renderer process, the origin and |
| // type must be bound. Once bound, a WorkletProcess may never become |
| // unbound. ReassignWorkletTypeAndOrigin() may only be called on an unbound |
| // process. |
| bool is_bound_to_origin_; |
| |
| // When a process is set idle, this timer will start to delete it after a |
| // fixed time to prevent holding onto unnecessary unused processes for too |
| // long. The timer will be cancelled if the process is set non-idle. |
| base::OneShotTimer remove_idle_process_from_manager_timer_; |
| |
| base::WeakPtrFactory<WorkletProcess> weak_ptr_factory_{this}; |
| }; |
| |
| // Class that tracks a request for an auction worklet process, and manages |
| // lifetime of the returned process once the request receives a process. |
| // Destroying the handle will abort a pending request and release any process |
| // it is keeping alive, so consumers should destroy these as soon as a process |
| // is no longer needed. |
| // |
| // A single process can be referenced by multiple handles. |
| class CONTENT_EXPORT ProcessHandle { |
| public: |
| ProcessHandle(); |
| ProcessHandle(const ProcessHandle&) = delete; |
| ProcessHandle& operator=(const ProcessHandle&) = delete; |
| ~ProcessHandle(); |
| |
| // Returns a non-null pointer once a ProcessHandle has been assigned a |
| // process. The pipe, however, may get broken if the process exits. |
| auction_worklet::mojom::AuctionWorkletService* GetService(); |
| |
| // Returns any RenderProcessHost being used to host this process, or |
| // nullptr. |
| RenderProcessHost* GetRenderProcessHostForTesting(); |
| |
| WorkletType worklet_type() const { return worklet_type_; } |
| |
| const url::Origin& origin() const { return origin_; } |
| |
| // Returns the underlying process assignment at this level. |
| // Meant for reference-equality testing. |
| const scoped_refptr<WorkletProcess>& worklet_process_for_testing() const { |
| return worklet_process_; |
| } |
| |
| const scoped_refptr<SiteInstance>& site_instance_for_testing() const { |
| return site_instance_; |
| } |
| |
| // Looks up which PID (from browser's perspective) this process is running |
| // in. If it's available immediately, it's returned. If not, nullopt is |
| // returned and |callback| will be invoked when it's available. Should not |
| // be called if the process hasn't been assigned yet. |
| std::optional<base::ProcessId> GetPid( |
| base::OnceCallback<void(base::ProcessId)> callback); |
| |
| // Tests can call this function to configure this ProcessHandle's worklet |
| // process's PID to this process. |
| void OnBaseProcessLaunchedForTesting(const base::Process& process) const; |
| |
| private: |
| friend class AuctionProcessManager; |
| friend class InRendererAuctionProcessManager; |
| friend class DedicatedAuctionProcessManager; |
| |
| // Assigns `worklet_process` to `this`. If `callback_` is non-null, queues a |
| // task to invoke it asynchronously, and GetService() will return nullptr |
| // until its invoked, so the consumer sees a consistent picture of the |
| // world. Destroying the Handle will cancel the pending callback. |
| void AssignProcess(scoped_refptr<WorkletProcess> worklet_process); |
| |
| void InvokeCallback(); |
| |
| base::OnceClosure callback_; |
| url::Origin origin_; |
| WorkletType worklet_type_; |
| |
| // SiteInstance representing the worklet. Used only by |
| // InRendererAuctionProcessManager. |
| scoped_refptr<SiteInstance> site_instance_; |
| |
| // Associated AuctionProcessManager. Set when a process is requested, |
| // cleared once a process is assigned (synchronously or asynchronously), |
| // since the AuctionProcessManager doesn't track Handles after they've been |
| // assigned processes - it tracks processes instead, at that point. |
| raw_ptr<AuctionProcessManager> manager_ = nullptr; |
| |
| scoped_refptr<WorkletProcess> worklet_process_; |
| |
| // Entry in the corresponding PendingRequestQueue if the handle has yet to |
| // be assigned a process. |
| std::list<raw_ptr<ProcessHandle, CtnExperimental>>::iterator |
| queued_request_; |
| |
| base::WeakPtrFactory<ProcessHandle> weak_ptr_factory_{this}; |
| }; |
| |
| AuctionProcessManager(const AuctionProcessManager&) = delete; |
| AuctionProcessManager& operator=(const AuctionProcessManager&) = delete; |
| virtual ~AuctionProcessManager(); |
| |
| // Requests a worklet service instance for a worklet with the specified |
| // properties. |
| // |
| // If a process is synchronously assigned to the ProcessHandle, returns true |
| // and the service pointer can immediately be retrieved from `process_handle`. |
| // `callback` will not be invoked. Otherwise, returns false and will invoke |
| // `callback` when the service pointer can be retrieved from `process_handle`. |
| // |
| // Auctions must request (and get) a service for their `kSeller` worklet |
| // before requesting any `kBidder` worklets to avoid deadlock. |
| // |
| // `frame_site_instance` must be the SiteInstance of the frame that requested |
| // the auction. It's only examined by InRendererAuctionProcessManager. |
| // |
| // Passed in ProcessHandles must be destroyed before the AuctionProcessManager |
| // is. ProcessHandles may not be reused. |
| // |
| // While `callback` is being invoked, it is fine to call into the |
| // AuctionProcessManager to request more WorkletServices, or even to delete |
| // the AuctionProcessManager, since nothing but the callback invocation is on |
| // the call stack. |
| [[nodiscard]] bool RequestWorkletService( |
| WorkletType worklet_type, |
| const url::Origin& origin, |
| scoped_refptr<SiteInstance> frame_site_instance, |
| ProcessHandle* process_handle, |
| base::OnceClosure callback); |
| |
| // Start an anticipatory process for an origin if |
| // 1) we have not yet started one for that buyer or seller origin and |
| // 2) we cannot use a shared process and |
| // 3) we have not yet reached the quota for the number of processes. |
| // An anticipatory process is a process for which we do not yet need |
| // a worklet; however, we anticipate that we will need a |
| // worklet for this origin later. This process will be owned by this |
| // AuctionProcessManger until it is needed. |
| void MaybeStartAnticipatoryProcess(const url::Origin& origin, |
| SiteInstance* frame_site_instance, |
| WorkletType worklet_type); |
| |
| size_t GetPendingBidderRequestsForTesting() const { |
| return pending_bidder_request_queue_.size(); |
| } |
| size_t GetPendingSellerRequestsForTesting() const { |
| return pending_seller_request_queue_.size(); |
| } |
| // Returns the count of non-idle bidder processes. |
| size_t GetBidderProcessCountForTesting() const { |
| return bidder_processes_.size(); |
| } |
| // Returns the count of non-idle seller processes. |
| size_t GetSellerProcessCountForTesting() const { |
| return seller_processes_.size(); |
| } |
| // Returns the count of idle processes, including for both bidders and |
| // sellers. |
| size_t GetIdleProcessCountForTesting() const { |
| return idle_processes_.size(); |
| } |
| |
| protected: |
| // `trusted_signals_cache` must outlive the AuctionProcessManager. Passing in |
| // a null cache means that there's no in-process KVv2 cache, because the |
| // feature is disabled. |
| explicit AuctionProcessManager( |
| TrustedSignalsCacheImpl* trusted_signals_cache); |
| |
| // Launches the actual process. The process will be kept-alive and |
| // watched by the returned WorkletProcess. |
| virtual scoped_refptr<WorkletProcess> LaunchProcess( |
| WorkletType worklet_type, |
| const url::Origin& origin, |
| scoped_refptr<SiteInstance> site_instance, |
| bool is_idle) = 0; |
| |
| // Hook called when a new process is assigned at the end of |
| // TryCreateOrGetProcessForHandle. This function is used for testing. |
| virtual void OnNewProcessAssigned(const ProcessHandle* process_handle) {} |
| |
| // Used to compute the value of `site_instance_` field of ProcessHandle. |
| // A subclass can return nullptr if it is not using SiteInstance to place |
| // worklets in appropriate renderers, but some other mechanism implementing a |
| // policy that's at least as strong as site isolation would be. |
| virtual scoped_refptr<SiteInstance> MaybeComputeSiteInstance( |
| SiteInstance* frame_site_instance, |
| const url::Origin& worklet_origin) = 0; |
| |
| // Tries to see if a shared process can be used for this, which will bypass |
| // the normal accounting logic and just use it. If it returns true, the |
| // process got assigned synchronously. There is no async case. |
| // |
| // `process_handle` will be already filled. |
| virtual bool TryUseSharedProcess(ProcessHandle* process_handle) = 0; |
| |
| private: |
| // Contains ProcessHandles which have not yet been assigned processes. |
| // Processes requested the earliest are at the start of the list, so processes |
| // can be assigned in FIFO order as process slots become available. A list is |
| // used to allow removal of cancelled requests, or requests that are assigned |
| // processes out of order (which happens in the case of bidder worklets when a |
| // bidder further up the queue with a matching owner receives a process). |
| // ProcessHandles are owned by consumers, and destroyed when they no longer |
| // need to keep their processes alive. |
| using PendingRequestQueue = |
| std::list<raw_ptr<ProcessHandle, CtnExperimental>>; |
| |
| // Contains ProcessHandles for bidder or seller requests which have not yet |
| // been assigned processes, indexed by origin. When the request in the |
| // PendingRequestQueue is assigned a process, all requests that can use the |
| // same process are assigned the same process. This map is used to manage that |
| // without searching through the entire queue. |
| using PendingRequestMap = |
| std::map<url::Origin, std::set<raw_ptr<ProcessHandle, SetExperimental>>>; |
| |
| // Contains running processes. Worklet processes are refcounted, and |
| // automatically remove themselves from this list when destroyed. |
| using ProcessMap = |
| std::map<url::Origin, raw_ptr<WorkletProcess, CtnExperimental>>; |
| |
| RequestWorkletServiceOutcome RequestWorkletServiceInternal( |
| WorkletType worklet_type, |
| const url::Origin& origin, |
| scoped_refptr<SiteInstance> frame_site_instance, |
| ProcessHandle* process_handle); |
| |
| // Tries to reuse an existing process for `process_handle` or create a new |
| // one. `process_handle`'s WorkletType and Origin must be populated. Respects |
| // the bidder and seller limits. |
| RequestWorkletServiceOutcome TryCreateOrGetProcessForHandle( |
| ProcessHandle* process_handle); |
| |
| // Attempts to get an idle process from `idle_processes_` |
| // to use with the handle. |
| bool TryToUseIdleProcessForHandle(ProcessHandle* process_handle); |
| |
| // Invoked by ProcessHandle's destructor, if it has previously been passed to |
| // RequestWorkletService(). Checks if a new seller worklet can be created. |
| void OnProcessHandleDestroyed(ProcessHandle* process_handle); |
| |
| // Removes `process_handle` from the `pending_bidder_requests_` or |
| // `pending_seller_requests_`, as appropriate. `process_handle` must be in one |
| // of those maps. |
| void RemovePendingProcessHandle(ProcessHandle* process_handle); |
| |
| // Invoked when WorkletProcess can no longer handle new requests, either |
| // because it was destroyed or because the underlying process died. Updates |
| // the corresponding ProcessMap, and checks if a new bidder process should be |
| // started. |
| void OnWorkletProcessUnusable(WorkletProcess* worklet_process); |
| |
| // Callback to call after an idle process times out so that we can |
| // release our hold of it. |
| void ReleaseIdleProcess(WorkletProcess* worklet_process); |
| |
| // Helpers to access the maps of the corresponding worklet type. |
| PendingRequestQueue* GetPendingRequestQueue(WorkletType worklet_type); |
| PendingRequestMap* GetPendingRequestMap(WorkletType worklet_type); |
| ProcessMap* Processes(WorkletType worklet_type); |
| |
| // Returns true if there's an available slot for an active process of the |
| // specified worklet type. |
| bool HasAvailableProcessSlotForActiveProcess(WorkletType worklet_type) const; |
| |
| // Returns true if there's an available slot for an idle process of the |
| // specified worklet type. |
| bool HasAvailableProcessSlotForIdleProcess( |
| WorkletType worklet_type, |
| size_t num_idle_processes_of_type) const; |
| |
| raw_ptr<TrustedSignalsCacheImpl> trusted_signals_cache_; |
| |
| PendingRequestQueue pending_bidder_request_queue_; |
| PendingRequestQueue pending_seller_request_queue_; |
| |
| PendingRequestMap pending_bidder_requests_; |
| PendingRequestMap pending_seller_requests_; |
| |
| ProcessMap bidder_processes_; |
| ProcessMap seller_processes_; |
| |
| // Idle processes sorted by creation time. These are processes that |
| // are not being actively used as a worklet but are on stand-by in case they |
| // are needed. |
| std::vector<scoped_refptr<WorkletProcess>> idle_processes_; |
| |
| base::WeakPtrFactory<AuctionProcessManager> weak_ptr_factory_{this}; |
| }; |
| |
| // An implementation of AuctionProcessManager that places worklet execution into |
| // dedicated utility processes, isolated by domain and role. |
| class CONTENT_EXPORT DedicatedAuctionProcessManager |
| : public AuctionProcessManager { |
| public: |
| explicit DedicatedAuctionProcessManager( |
| TrustedSignalsCacheImpl* trusted_signals_cache); |
| ~DedicatedAuctionProcessManager() override; |
| |
| protected: |
| // Virtual for testing. Takes `worklet_process` so that test classes can get |
| // WeakPtrs to it for creation order tracking. |
| virtual WorkletProcess::ProcessContext CreateProcessInternal( |
| WorkletProcess& worklet_process); |
| |
| private: |
| // AuctionProcessManager implementation: |
| scoped_refptr<WorkletProcess> LaunchProcess( |
| WorkletType worklet_type, |
| const url::Origin& origin, |
| scoped_refptr<SiteInstance> site_instance, |
| bool is_idle) override; |
| scoped_refptr<SiteInstance> MaybeComputeSiteInstance( |
| SiteInstance* frame_site_instance, |
| const url::Origin& worklet_origin) override; |
| bool TryUseSharedProcess(ProcessHandle* process_handle) override; |
| }; |
| |
| // An alternative implementation of AuctionProcessManager that places worklet |
| // execution into regular renderer processes (rather than worklet-only utility |
| // processes) following the site isolation policy. |
| class CONTENT_EXPORT InRendererAuctionProcessManager |
| : public AuctionProcessManager { |
| public: |
| explicit InRendererAuctionProcessManager( |
| TrustedSignalsCacheImpl* trusted_signals_cache); |
| ~InRendererAuctionProcessManager() override; |
| |
| protected: |
| // Virtual for testing. Takes `worklet_process` so that test classes can get |
| // WeakPtrs to it for creation order tracking. |
| virtual WorkletProcess::ProcessContext CreateProcessInternal( |
| WorkletProcess& worklet_process); |
| |
| private: |
| // AuctionProcessManager implementation: |
| scoped_refptr<WorkletProcess> LaunchProcess( |
| WorkletType worklet_type, |
| const url::Origin& origin, |
| scoped_refptr<SiteInstance> site_instance, |
| bool is_idle) override; |
| scoped_refptr<SiteInstance> MaybeComputeSiteInstance( |
| SiteInstance* frame_site_instance, |
| const url::Origin& worklet_origin) override; |
| bool TryUseSharedProcess(ProcessHandle* process_handle) override; |
| }; |
| |
| } // namespace content |
| |
| #endif // CONTENT_BROWSER_INTEREST_GROUP_AUCTION_PROCESS_MANAGER_H_ |