Page Menu
Home
WickedGov Phorge
Search
Configure Global Search
Log In
Files
F6030178
OrderedStreamingForkController.php
No One
Temporary
Actions
Download File
Edit File
Delete File
View Transforms
Subscribe
Flag For Later
Award Token
Size
5 KB
Referenced Files
None
Subscribers
None
OrderedStreamingForkController.php
View Options
<?php
/**
* @license GPL-2.0-or-later
* @file
*/
namespace
MediaWiki\Maintenance
;
/**
* Apply a transformation to values via a pool of sub processes.
*
* The controller reads lines from a given input stream, where each line
* describes work to be done. This work is then farmed out to multiple
* child streams that correspond to child procesess. Each child has exactly
* one piece of work in-flight at a given moment. The result of each work
* is written to an output stream.
*
* If numProcs is zero, the fallback is to perform work in-process instead.
*
* This class guarantees that the output is produced in the same exact order
* as input values were.
*
* Currently used by CirrusSearch extension to implement CLI search script.
*
* @ingroup Maintenance
* @since 1.30
*/
class
OrderedStreamingForkController
extends
ForkController
{
/** @var callable */
protected
$workCallback
;
/** @var resource */
protected
$input
;
/** @var resource */
protected
$output
;
/** @var int */
protected
$nextOutputId
;
/** @var string[] Int key indicates order, value is data */
protected
$delayedOutputData
=
[];
/**
* @param int $numProcs The number of worker processes to fork
* @param callable $workCallback A callback to call in the child process
* once for each line of work to process.
* @param resource $input A socket to read work lines from
* @param resource $output A socket to write the result of work to.
*/
public
function
__construct
(
$numProcs
,
$workCallback
,
$input
,
$output
)
{
parent
::
__construct
(
$numProcs
);
$this
->
workCallback
=
$workCallback
;
$this
->
input
=
$input
;
$this
->
output
=
$output
;
}
/**
* @inheritDoc
*/
public
function
start
()
{
if
(
$this
->
procsToStart
>
0
)
{
$status
=
parent
::
start
();
if
(
$status
===
'child'
)
{
$this
->
consume
();
}
}
else
{
$status
=
'parent'
;
$this
->
consumeNoFork
();
}
return
$status
;
}
/**
* @param int $numProcs
* @return string
*/
protected
function
forkWorkers
(
$numProcs
)
{
$this
->
prepareEnvironment
();
$childSockets
=
[];
// Create the child processes
for
(
$i
=
0
;
$i
<
$numProcs
;
$i
++
)
{
$sockets
=
stream_socket_pair
(
STREAM_PF_UNIX
,
STREAM_SOCK_STREAM
,
STREAM_IPPROTO_IP
);
// Do the fork
$pid
=
pcntl_fork
();
if
(
$pid
===
-
1
)
{
echo
"Error creating child processes
\n
"
;
exit
(
1
);
}
if
(
!
$pid
)
{
$this
->
initChild
();
$this
->
childNumber
=
$i
;
$this
->
input
=
$sockets
[
0
];
$this
->
output
=
$sockets
[
0
];
fclose
(
$sockets
[
1
]
);
return
'child'
;
}
else
{
// This is the parent process
$this
->
children
[
$pid
]
=
true
;
fclose
(
$sockets
[
0
]
);
$childSockets
[]
=
$sockets
[
1
];
}
}
$this
->
feedChildren
(
$childSockets
);
foreach
(
$childSockets
as
$socket
)
{
fclose
(
$socket
);
}
return
'parent'
;
}
/**
* Child worker process. Reads work from $this->input and writes the
* result of that work to $this->output when completed.
*/
protected
function
consume
()
{
while
(
!
feof
(
$this
->
input
)
)
{
$line
=
trim
(
fgets
(
$this
->
input
)
);
if
(
$line
)
{
[
$id
,
$data
]
=
json_decode
(
$line
);
$result
=
(
$this
->
workCallback
)(
$data
);
fwrite
(
$this
->
output
,
json_encode
(
[
$id
,
$result
]
)
.
"
\n
"
);
}
}
}
/**
* Special cased version of self::consume() when no forking occurs
*/
protected
function
consumeNoFork
()
{
while
(
!
feof
(
$this
->
input
)
)
{
$data
=
fgets
(
$this
->
input
);
if
(
substr
(
$data
,
-
1
)
===
"
\n
"
)
{
// Strip any final new line used to delimit lines of input.
// The last line of input might not have it, though.
$data
=
substr
(
$data
,
0
,
-
1
);
}
if
(
$data
===
''
)
{
continue
;
}
$result
=
(
$this
->
workCallback
)(
$data
);
fwrite
(
$this
->
output
,
"$result
\n
"
);
}
}
/**
* Reads lines of work from $this->input and farms them out to
* the provided socket.
*
* @param resource[] $sockets
*/
protected
function
feedChildren
(
array
$sockets
)
{
$used
=
[];
$id
=
0
;
$this
->
nextOutputId
=
0
;
while
(
!
feof
(
$this
->
input
)
)
{
$data
=
fgets
(
$this
->
input
);
if
(
$used
)
{
do
{
$this
->
updateAvailableSockets
(
$sockets
,
$used
,
$sockets
?
0
:
5
);
}
while
(
!
$sockets
);
}
if
(
substr
(
$data
,
-
1
)
===
"
\n
"
)
{
// Strip any final new line used to delimit lines of input.
// The last line of input might not have it, though.
$data
=
substr
(
$data
,
0
,
-
1
);
}
if
(
$data
===
''
)
{
continue
;
}
$socket
=
array_pop
(
$sockets
);
fwrite
(
$socket
,
json_encode
(
[
$id
++,
$data
]
)
.
"
\n
"
);
$used
[]
=
$socket
;
}
while
(
$used
)
{
$this
->
updateAvailableSockets
(
$sockets
,
$used
,
5
);
}
}
/**
* Moves sockets from $used to $sockets when they are available
* for more work
*
* @param resource[] &$sockets List of sockets that are waiting for work
* @param resource[] &$used List of sockets currently performing work
* @param int $timeout The number of seconds to block waiting. 0 for
* non-blocking operation.
*/
protected
function
updateAvailableSockets
(
&
$sockets
,
&
$used
,
$timeout
)
{
$read
=
$used
;
$write
=
$except
=
[];
stream_select
(
$read
,
$write
,
$except
,
$timeout
);
foreach
(
$read
as
$socket
)
{
$line
=
fgets
(
$socket
);
[
$id
,
$data
]
=
json_decode
(
trim
(
$line
)
);
$this
->
receive
(
(
int
)
$id
,
$data
);
$sockets
[]
=
$socket
;
$idx
=
array_search
(
$socket
,
$used
);
unset
(
$used
[
$idx
]
);
}
}
/**
* @param int $id
* @param string $data
*/
protected
function
receive
(
$id
,
$data
)
{
if
(
$id
!==
$this
->
nextOutputId
)
{
$this
->
delayedOutputData
[
$id
]
=
$data
;
return
;
}
fwrite
(
$this
->
output
,
$data
.
"
\n
"
);
$this
->
nextOutputId
=
$id
+
1
;
while
(
isset
(
$this
->
delayedOutputData
[
$this
->
nextOutputId
]
)
)
{
fwrite
(
$this
->
output
,
$this
->
delayedOutputData
[
$this
->
nextOutputId
]
.
"
\n
"
);
unset
(
$this
->
delayedOutputData
[
$this
->
nextOutputId
]
);
$this
->
nextOutputId
++;
}
}
}
/** @deprecated class alias since 1.40 */
class_alias
(
OrderedStreamingForkController
::
class
,
'OrderedStreamingForkController'
);
File Metadata
Details
Attached
Mime Type
text/x-php
Expires
Oct 4 2026, 17:44 (5 w, 4 d ago)
Storage Engine
local-disk
Storage Format
Raw Data
Storage Handle
01/1e/0a09a1dcfdf77b34616c627d45aa
Default Alt Text
OrderedStreamingForkController.php (5 KB)
Attached To
Mode
rMWPROD MediaWiki Production
Attached
Detach File
Event Timeline
Log In to Comment