parallel icon indicating copy to clipboard operation
parallel copied to clipboard

amphp/parallel + ssh2

Open hitriyvalenok opened this issue 1 year ago • 6 comments

Hello everyone! I like and very appreciate your work! Great library and docs!

I am trying to use ssh2 extension with parallel. Unfortunately, I am getting a mysterious issue. When I am calling a method in the object of my SSH class (for example, exec), the connection variable, initialized in the constructor, always resets to a "0" value instead of a resource. Full error looks like that:

Uncaught TypeError thrown in context with message "ssh2_exec(): Argument #1 ($session) must be of type resource, int given" and code "0" in SSH.php:18

What I am doing wrong? Thank you!

Main.php:

<?php
error_reporting( E_ALL & ~E_DEPRECATED );

use Amp\Parallel\Worker\ContextWorkerPool;
use Par\SSHExecTask;

require_once __DIR__ . '/vendor/autoload.php';

$pool = new ContextWorkerPool( 1 );
$ssh = new \Par\SSH( '127.0.0.1', 322, 'user', '123456' );
$task = $pool->submit( new SSHExecTask( $ssh ) );

$response = $task->await();
var_dump( $response );

src/SSH.php:

<?php
namespace Par;

class SSH
{
    private $connection;
    private $sftp;

    public function __construct( $host, $port, $user, $password )
    {
        $this->connection = ssh2_connect( $host, $port );
        ssh2_auth_password( $this->connection, $user, $password );
        $this->sftp = ssh2_sftp( $this->connection );
    }

    public function exec( $command )
    {
        $stdout = ssh2_exec( $this->connection, $command );
        $stderr = ssh2_fetch_stream( $stdout, SSH2_STREAM_STDERR );

        stream_set_blocking( $stdout, true );
        stream_set_blocking( $stderr, true );

        $stdoutContent = stream_get_contents( $stdout );
        $stderrContent = stream_get_contents( $stderr );

        if( $stderrContent ) {
            throw new \RuntimeException( $stderrContent );
        }

        fclose( $stdout );
        fclose( $stderr );

        return trim( $stdoutContent );
    }

    public function upload( $localFile, $remoteFile )
    {
        $stream = fopen( "ssh2.sftp://{$this->sftp}/$remoteFile", 'w' );
        return fwrite( $stream, file_get_contents( $localFile ) );
    }

    public function mkdir( $dirname, $mod = 0777, $recursive = false )
    {
        return ssh2_sftp_mkdir( $this->sftp, $dirname, $mod, $recursive );
    }
}

src/SSHExecTask.php:

<?php
namespace Par;

use Amp\Cancellation;
use Amp\Parallel\Worker\Task;
use Amp\Sync\Channel;

class SSHExecTask implements Task
{
    public function __construct(
        private readonly SSH $ssh
    ) {}

    public function run( Channel $channel, Cancellation $cancellation ): bool
    {
        return $this->ssh->exec( 'echo 123' );
    }
}

composer.json:

{
    "require": {
        "amphp/parallel": "^2.1",
        "ext-ssh2": "*"
    },
    "autoload": {
        "psr-4": {
            "Par\\": "src"
        }
    }
}

hitriyvalenok avatar Apr 21 '23 11:04 hitriyvalenok

I've built a project you could deploy. You need PHP 8.0<= and ext-ssh2.

project.zip

hitriyvalenok avatar Apr 21 '23 11:04 hitriyvalenok

You can't share objects over multiple parallel contexts. Objects are serialized and unserialized in the other context, so your connection will be lost during serialization.

kelunik avatar Apr 21 '23 13:04 kelunik

You can't share objects over multiple parallel contexts. Objects are serialized and unserialized in the other context, so your connection will be lost during serialization.

Thanks! I'm starting to understand...

Could you please say why when a thread working my terminal sees nothing sent by echo/printf? I'm using logs to fix the issue, but it isn't very comfortable :(

hitriyvalenok avatar Apr 21 '23 14:04 hitriyvalenok

These parallel contexts are child processes with their own standard input and output streams. I think we do have some automatic forwarding to the parent streams, but I'm not too sure on that.

kelunik avatar Apr 21 '23 14:04 kelunik

You can't share objects over multiple parallel contexts. Objects are serialized and unserialized in the other context, so your connection will be lost during serialization.

I feel myself a stupid puppy :( Generally, I have two cute objects I'd like to send in the thread to use in. You know, it's about good practice. But I don't understand how it will be going in the context of parallel? What I can do with that? Do there any other parallel-like tools where I can do manage it better?

hitriyvalenok avatar Apr 21 '23 14:04 hitriyvalenok

No, you'll have this problem with any PHP library that does similar things. You can pass the connection details to the child context instead of the connection itself and establish a new connection in every child.

kelunik avatar Apr 21 '23 15:04 kelunik