1
    2
    3
    4
    5
    6
    7
    8
    9
   10
   11
   12
   13
   14
   15
   16
   17
   18
   19
   20
   21
   22
   23
   24
   25
   26
   27
   28
   29
   30
   31
   32
   33
   34
   35
   36
   37
   38
   39
   40
   41
   42
   43
   44
   45
   46
   47
   48
   49
   50
   51
   52
   53
   54
   55
   56
   57
   58
   59
   60
   61
   62
   63
   64
   65
   66
   67
   68
   69
   70
   71
   72
   73
   74
   75
   76
   77
   78
   79
   80
   81
   82
   83
   84
   85
   86
   87
   88
   89
   90
   91
   92
   93
   94
   95
   96
   97
   98
   99
  100
  101
  102
  103
  104
  105
  106
  107
  108
  109
  110
  111
  112
  113
  114
  115
  116
  117
  118
  119
  120
  121
  122
  123
  124
  125
  126
  127
  128
  129
  130
  131
  132
  133
  134
  135
  136
  137
  138
  139
  140
  141
  142
  143
  144
  145
  146
  147
  148
  149
  150
  151
  152
  153
  154
  155
  156
  157
  158
  159
  160
  161
  162
  163
  164
  165
  166
  167
  168
  169
  170
  171
  172
  173
  174
  175
  176
  177
  178
  179
  180
  181
  182
  183
  184
  185
  186
  187
  188
  189
  190
  191
  192
  193
  194
  195
  196
  197
  198
  199
  200
  201
  202
  203
  204
  205
  206
  207
  208
  209
  210
  211
  212
  213
  214
  215
  216
  217
  218
  219
  220
  221
  222
  223
  224
  225
  226
  227
  228
  229
  230
  231
  232
  233
  234
  235
  236
  237
  238
  239
  240
  241
  242
  243
  244
  245
  246
  247
  248
  249
  250
  251
  252
  253
  254
  255
  256
  257
  258
  259
  260
  261
  262
  263
  264
  265
  266
  267
  268
  269
  270
  271
  272
  273
  274
  275
  276
  277
  278
  279
  280
  281
  282
  283
  284
  285
  286
  287
  288
  289
  290
  291
  292
  293
  294
  295
  296
  297
  298
  299
  300
  301
  302
  303
  304
  305
  306
  307
  308
  309
  310
  311
  312
  313
  314
  315
  316
  317
  318
  319
  320
  321
  322
  323
  324
  325
  326
  327
  328
  329
  330
  331
  332
  333
  334
  335
  336
  337
  338
  339
  340
  341
  342
  343
  344
  345
  346
  347
  348
  349
  350
  351
  352
  353
  354
  355
  356
  357
  358
  359
  360
  361
  362
  363
  364
  365
  366
  367
  368
  369
  370
  371
  372
  373
  374
  375
  376
  377
  378
  379
  380
  381
  382
  383
  384
  385
  386
  387
  388
  389
  390
  391
  392
  393
  394
  395
  396
  397
  398
  399
  400
  401
  402
  403
  404
  405
  406
  407
  408
  409
  410
  411
  412
  413
  414
  415
  416
  417
  418
  419
  420
  421
  422
  423
  424
  425
  426
  427
  428
  429
  430
  431
  432
  433
  434
  435
  436
  437
  438
  439
  440
  441
  442
  443
  444
  445
  446
  447
  448
  449
  450
  451
  452
  453
  454
  455
  456
  457
  458
  459
  460
  461
  462
  463
  464
  465
  466
  467
  468
  469
  470
  471
  472
  473
  474
  475
  476
  477
  478
  479
  480
  481
  482
  483
  484
  485
  486
  487
  488
  489
  490
  491
  492
  493
  494
  495
  496
  497
  498
  499
  500
  501
  502
  503
  504
  505
  506
  507
  508
  509
  510
  511
  512
  513
  514
  515
  516
  517
  518
  519
  520
  521
  522
  523
  524
  525
  526
  527
  528
  529
  530
  531

content / browser / interest_group / auction_process_manager.h [blame]

// 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;

// 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.
  class CONTENT_EXPORT WorkletProcess : public base::RefCounted<WorkletProcess>,
                                        public RenderProcessHostObserver {
   public:
    // `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,
        RenderProcessHost* render_process_host,
        mojo::PendingRemote<auction_worklet::mojom::AuctionWorkletService>
            service,
        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);

    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);

   private:
    friend class base::RefCounted<WorkletProcess>;
    friend class DedicatedAuctionProcessManager;

    // From RenderProcessHostObserver:
    void RenderProcessReady(RenderProcessHost* host) override;

    void RenderProcessHostDestroyed(RenderProcessHost* host) override;

    void RemoveFromProcessManager(bool on_destruction);

    ~WorkletProcess() override;

    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);

   private:
    friend class ProcessHandleTestPeer;
    friend class AuctionProcessManager;
    friend class InRendererAuctionProcessManager;
    friend class DedicatedAuctionProcessManager;

    // Tests can call this function to configure this ProcessHandle's worklet
    // process's PID to this process.
    void OnBaseProcessLaunchedForTesting(const base::Process& process) const;

    // 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:
  AuctionProcessManager();

  // 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,
      const std::string& display_name,
      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;

  // Returns the display name to use for a process. Separate method so it can be
  // used in tests.
  static std::string ComputeDisplayName(WorkletType worklet_type,
                                        const url::Origin& origin);

 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;

  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:
  DedicatedAuctionProcessManager();
  ~DedicatedAuctionProcessManager() override;

 private:
  scoped_refptr<WorkletProcess> LaunchProcess(
      WorkletType worklet_type,
      const url::Origin& origin,
      scoped_refptr<SiteInstance> site_instance,
      const std::string& display_name,
      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:
  InRendererAuctionProcessManager();
  ~InRendererAuctionProcessManager() override;

 protected:
  scoped_refptr<WorkletProcess> LaunchProcess(
      WorkletType worklet_type,
      const url::Origin& origin,
      scoped_refptr<SiteInstance> site_instance,
      const std::string& display_name,
      bool is_idle) override;

  scoped_refptr<SiteInstance> MaybeComputeSiteInstance(
      SiteInstance* frame_site_instance,
      const url::Origin& worklet_origin) override;
  bool TryUseSharedProcess(ProcessHandle* process_handle) override;

 private:
  RenderProcessHost* LaunchInSiteInstance(
      SiteInstance* site_instance,
      mojo::PendingReceiver<auction_worklet::mojom::AuctionWorkletService>
          auction_worklet_service_receiver);
};

}  // namespace content

#endif  // CONTENT_BROWSER_INTEREST_GROUP_AUCTION_PROCESS_MANAGER_H_