Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 57
0.00% covered (danger)
0.00%
0 / 6
CRAP
0.00% covered (danger)
0.00%
0 / 1
PoolCounterClient
0.00% covered (danger)
0.00%
0 / 57
0.00% covered (danger)
0.00%
0 / 6
552
0.00% covered (danger)
0.00%
0 / 1
 setManager
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getConn
0.00% covered (danger)
0.00%
0 / 8
0.00% covered (danger)
0.00%
0 / 1
12
 sendCommand
0.00% covered (danger)
0.00%
0 / 33
0.00% covered (danger)
0.00%
0 / 1
182
 acquireForMe
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
6
 acquireForAnyone
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
6
 release
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
6
1<?php
2/**
3 * This program is free software; you can redistribute it and/or modify
4 * it under the terms of the GNU General Public License as published by
5 * the Free Software Foundation; either version 2 of the License, or
6 * (at your option) any later version.
7 *
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
12 *
13 * You should have received a copy of the GNU General Public License along
14 * with this program; if not, write to the Free Software Foundation, Inc.,
15 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16 * http://www.gnu.org/copyleft/gpl.html
17 *
18 * @file
19 */
20
21namespace MediaWiki\PoolCounter;
22
23use MediaWiki\Status\Status;
24
25/**
26 * @since 1.16
27 */
28class PoolCounterClient extends PoolCounter {
29    /**
30     * @var ?resource the socket connection to the poolcounterd.  Closing this
31     * releases all locks acquired.
32     */
33    private $conn;
34
35    /**
36     * @var string The server host name
37     */
38    private $hostName;
39
40    /**
41     * @var PoolCounterConnectionManager
42     */
43    private $manager;
44
45    public function setManager( PoolCounterConnectionManager $manager ): void {
46        $this->manager = $manager;
47    }
48
49    /**
50     * @return Status
51     */
52    public function getConn() {
53        if ( !$this->conn ) {
54            $status = $this->manager->get( $this->key );
55            if ( !$status->isOK() ) {
56                return $status;
57            }
58            // @phan-suppress-next-line PhanTypeArraySuspiciousNullable
59            $this->conn = $status->value['conn'];
60            // @phan-suppress-next-line PhanTypeArraySuspiciousNullable
61            $this->hostName = $status->value['hostName'];
62
63            // Set the read timeout to be 1.5 times the pool timeout.
64            // This allows the server to time out gracefully before we give up on it.
65            stream_set_timeout( $this->conn, 0, (int)( $this->timeout * 1e6 * 1.5 ) );
66        }
67        // TODO: Convert from Status to StatusValue
68        return Status::newGood( $this->conn );
69    }
70
71    /**
72     * @param string|int|float ...$args
73     * @return Status
74     */
75    public function sendCommand( ...$args ) {
76        $args = str_replace( ' ', '%20', $args );
77        $cmd = implode( ' ', $args );
78        $status = $this->getConn();
79        if ( !$status->isOK() ) {
80            return $status;
81        }
82        $conn = $status->value;
83        $this->logger->debug( "Sending pool counter command: $cmd" );
84        if ( fwrite( $conn, "$cmd\n" ) === false ) {
85            return Status::newFatal( 'poolcounter-write-error', $this->hostName );
86        }
87        $response = fgets( $conn );
88        if ( $response === false ) {
89            return Status::newFatal( 'poolcounter-read-error', $this->hostName );
90        }
91        $response = rtrim( $response, "\r\n" );
92        $this->logger->debug( "Got pool counter response: $response" );
93        $parts = explode( ' ', $response, 2 );
94        $responseType = $parts[0];
95        switch ( $responseType ) {
96            case 'LOCKED':
97                $this->onAcquire();
98                break;
99            case 'RELEASED':
100                $this->onRelease();
101                break;
102            case 'DONE':
103            case 'NOT_LOCKED':
104            case 'QUEUE_FULL':
105            case 'TIMEOUT':
106            case 'LOCK_HELD':
107                break;
108            case 'ERROR':
109            default:
110                $parts = explode( ' ', $parts[1], 2 );
111                $errorMsg = $parts[1] ?? '(no message given)';
112                return Status::newFatal( 'poolcounter-remote-error', $errorMsg, $this->hostName );
113        }
114        return Status::newGood( constant( "PoolCounter::$responseType" ) );
115    }
116
117    /**
118     * @param int|null $timeout
119     * @return Status
120     */
121    public function acquireForMe( $timeout = null ) {
122        $status = $this->precheckAcquire();
123        if ( !$status->isGood() ) {
124            return $status;
125        }
126        return $this->sendCommand( 'ACQ4ME', $this->key, $this->workers, $this->maxqueue,
127            $timeout ?? $this->timeout );
128    }
129
130    /**
131     * @param int|null $timeout
132     * @return Status
133     */
134    public function acquireForAnyone( $timeout = null ) {
135        $status = $this->precheckAcquire();
136        if ( !$status->isGood() ) {
137            return $status;
138        }
139        return $this->sendCommand( 'ACQ4ANY', $this->key, $this->workers, $this->maxqueue,
140            $timeout ?? $this->timeout );
141    }
142
143    /**
144     * @return Status
145     */
146    public function release() {
147        $status = $this->sendCommand( 'RELEASE' );
148
149        if ( $this->conn ) {
150            $this->manager->close( $this->conn );
151            $this->conn = null;
152        }
153
154        return $status;
155    }
156}