MediaWiki master
PoolCounterConnectionManager.php
Go to the documentation of this file.
1<?php
21namespace MediaWiki\PoolCounter;
22
23use InvalidArgumentException;
25use Wikimedia\IPUtils;
26
35 public $hostNames;
37 public $conns = [];
39 public $refCounts = [];
41 public $timeout;
44
49 public $host;
50
55 public $port;
56
60 public function __construct( $conf ) {
61 if ( !count( $conf['servers'] ) ) {
62 throw new InvalidArgumentException( __METHOD__ . ': no servers configured' );
63 }
64 $this->hostNames = $conf['servers'];
65 $this->timeout = $conf['timeout'] ?? 0.1;
66 $this->connect_timeout = $conf['connect_timeout'] ?? 0;
67 }
68
73 public function get( $key ) {
74 $hashes = [];
75 foreach ( $this->hostNames as $hostName ) {
76 $hashes[$hostName] = md5( $hostName . $key );
77 }
78 asort( $hashes );
79 $errno = 0;
80 $errstr = '';
81 $hostName = '';
82 $conn = null;
83 foreach ( $hashes as $hostName => $hash ) {
84 if ( isset( $this->conns[$hostName] ) ) {
85 $this->refCounts[$hostName]++;
86 return Status::newGood(
87 [ 'conn' => $this->conns[$hostName], 'hostName' => $hostName ] );
88 }
89 $parts = IPUtils::splitHostAndPort( $hostName );
90 if ( $parts === false ) {
91 $errstr = '\'servers\' config incorrectly configured.';
92 return Status::newFatal( 'poolcounter-connection-error', $errstr, $hostName );
93 }
94 // IPV6 addresses need to be in brackets otherwise it fails.
95 $this->host = IPUtils::isValidIPv6( $parts[0] ) ? '[' . $parts[0] . ']' : $parts[0];
96 $this->port = $parts[1] ?: 7531;
97 // phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged
98 $conn = @$this->open( $this->host, $this->port, $errno, $errstr );
99 if ( $conn ) {
100 break;
101 }
102 }
103 if ( !$conn ) {
104 return Status::newFatal( 'poolcounter-connection-error', $errstr, $hostName );
105 }
106 // TODO: Inject PSR Logger from ServiceWiring
107 wfDebug( "Connected to pool counter server: $hostName\n" );
108 $this->conns[$hostName] = $conn;
109 $this->refCounts[$hostName] = 1;
110 return Status::newGood( [ 'conn' => $conn, 'hostName' => $hostName ] );
111 }
112
121 private function open( $host, $port, &$errno, &$errstr ) {
122 // If connect_timeout is set, we try to open the socket twice.
123 // You usually want to set the connection timeout to a very
124 // small value so that in case of failure of a server the
125 // connection to poolcounter is not a SPOF.
126 if ( $this->connect_timeout > 0 ) {
127 $tries = 2;
129 } else {
130 $tries = 1;
132 }
133
134 $fp = null;
135 while ( true ) {
136 $fp = fsockopen( $host, $port, $errno, $errstr, $timeout );
137 if ( $fp !== false || --$tries < 1 ) {
138 break;
139 }
140 usleep( 1000 );
141 }
142
143 return $fp;
144 }
145
149 public function close( $conn ) {
150 foreach ( $this->conns as $hostName => $otherConn ) {
151 if ( $conn === $otherConn ) {
152 if ( $this->refCounts[$hostName] ) {
153 $this->refCounts[$hostName]--;
154 }
155 if ( !$this->refCounts[$hostName] ) {
156 fclose( $conn );
157 unset( $this->conns[$hostName] );
158 }
159 }
160 }
161 }
162}
wfDebug( $text, $dest='all', array $context=[])
Sends a line to the debug log if enabled or, optionally, to a comment in output.
Helper for \MediaWiki\PoolCounter\PoolCounterClient.
Generic operation result class Has warning/error list, boolean status and arbitrary value.
Definition Status.php:54