chromium/printing/backend/cups_connection_pool.cc

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

#include "printing/backend/cups_connection_pool.h"

#include <cups/cups.h>

#include "base/check.h"
#include "base/containers/queue.h"
#include "base/logging.h"
#include "base/sequence_checker.h"
#include "printing/backend/cups_connection.h"
#include "printing/backend/cups_deleters.h"
#include "printing/backend/cups_helper.h"

namespace printing {

namespace {

// There needs to be a separate connection for each thread which will connect to
// the CUPS server.  While the CUPS library is thread safe, a connection cannot
// be shared between threads.
// Issuing multiple print jobs concurrently to a single destination can be
// be queued up for submission across the same connection (they would be
// serialized at the device anyway).  There is benefit to supporting concurrent
// printing to different destinations.  It is unlikely that this would be done
// to more than a handful of destinations at once.  Pick a somewhat arbitrary
// low number for the number of allowed connections, knowing that one of these
// connections will be needed by PrintBackend.  If a user were to swamp printing
// with jobs to more unique destinations at the same time then the PrintBackend
// service could be made to throttle those and queue them up awaiting a
// connection to use.
constexpr int kNumCupsConnections = 5;

CupsConnectionPool* g_cups_connection_pool_singleton = nullptr;

}  // namespace

CupsConnectionPool::CupsConnectionPool() = default;

CupsConnectionPool::~CupsConnectionPool() = default;

// static
void CupsConnectionPool::Create() {
  DCHECK(!g_cups_connection_pool_singleton);

  // The pool is only used for connections to default server over the default
  // IPP port.  These connections are never closed; they are reused until the
  // the process terminates.
  const int port = ippPort();
  const char* server = cupsServer();
  g_cups_connection_pool_singleton = new CupsConnectionPool();
  VLOG(1) << "Creating CUPS connection pool seeded with " << kNumCupsConnections
          << " connections";
  for (auto i = 0; i < kNumCupsConnections; ++i) {
    ScopedHttpPtr connection = HttpConnect2(
        server, port, /*addrlist=*/nullptr, AF_UNSPEC, HTTP_ENCRYPTION_NEVER,
        /*blocking*/ 0, kCupsTimeoutMs, /*cancel=*/nullptr);
    if (!connection) {
      LOG(ERROR) << "Unable to create CUPS connection: "
                 << cupsLastErrorString();
      break;
    }
    g_cups_connection_pool_singleton->AddConnection(std::move(connection));
  }
}

// static
CupsConnectionPool* CupsConnectionPool::GetInstance() {
  return g_cups_connection_pool_singleton;
}

ScopedHttpPtr CupsConnectionPool::TakeConnection() {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  if (connections_.empty())
    return nullptr;

  ScopedHttpPtr connection = std::move(connections_.front());
  connections_.pop();
  return connection;
}

void CupsConnectionPool::AddConnection(ScopedHttpPtr connection) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  DCHECK(connection);
  connections_.emplace(std::move(connection));
}

}  //  namespace printing