-
Notifications
You must be signed in to change notification settings - Fork 66
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Support protocol packages larger than 16 MiB #47 #166
Conversation
Hey @dmarkic thanks for opening this pull request 👍 Can you also add additional unit tests so we can assure that your code works as expected. This is also a great way to test against different edge cases and see if we thought of everything important. |
…l packages larger than 16 MiB #47
Hey @SimonFrings , |
@dmarkic Thanks for adding tests 👍 I took a look at the results from GitHub actions and saw that it shows risky tests. It seems like your assertions are never executed. I think what's happening here is that the $connection->query('select \'' . $text . '\'')->then(
function (QueryResult $command) use ($text) {
$this->assertCount(1, $command->resultRows);
$this->assertCount(1, $command->resultRows[0]);
$this->assertSame($text, reset($command->resultRows[0]));
$this->assertInstanceOf('React\MySQL\Connection', $conn);
},
function (Exception $error) {
echo 'Error: ' . $error->getMessage() . PHP_EOL;
}
); You can try this out and look for yourself if any exception is thrown, but I don't recommend adding this to your tests and then commit it to this PR. This way we stay consistent with the other tests in the I know the current behavior when it comes to exception handling is confusing, but we're going to change that with the upcoming v3 for ReactPHP, so that all errors should be shown by default unless explicitly hidden. For more information about our plans for ReactPHP v3, take a look at our v3 announcement. I really like that you added tests here, in particular you tested the functional side which is totally fine in itself and the right thing to do, but you also have to take a look at the unit side. Can be kind of confusing that we have to types of tests, so I'll quickly break it down:
I'm sorry for the big wall of text here, I tried to give you as much information as possible to help you keep going. I'm happy to help more if you have any further questions 👍 |
Thank you for your thorough explanation. Regarding the failed I can write additional Parser tests that would test split packet functionality. I was first going with this functional part first, which is where the real server is tested against the code. Is there a way to increase I would suggest however, to change the tests in a way like I've updated the tests so the Let me know your thoughts. I'll push the changes in few minutes, so we can see the exception. Thanks! |
It seems that all tests fail because of |
@dmarkic So sounds like |
@SimonFrings , for tests to work, the server on which tests are performed, needs to have See here: https://github.com/friends-of-reactphp/mysql/actions/runs/3565935698/jobs/5991738760#step:7:62 In this case, the mysql docker server needs If Thanks! |
@dmarkic So I guess you're using the docker example from our Another thing I want to mention here is that we can test this on a unit level so this project behaves the right way, but this doesn't mean that every server will accept these packet sizes and that's why this is "difficult" to test the functional side. For example, if you test the functional side on your local machine with a changed server configuration, it is likely that all tests will pass, but on GitHub actions the same tests will fail, because we never adjusted the configuration there. One answer could be that we just raise the
Another way could be to write a test that will check the If you ask me, I would always suggest to split your big packet into smaller ones if possible, maybe this could also be an answer to your problem. I don't know if there are cases where it is impossible to use smaller packets. If there are it is a good idea that this project support bigger packages.But like I said above, how do we test this the right way? A lot of questions, I'm interested in your opinion on this 👍 |
The only question here is: can/will you adjust the Sure, if someone would want to run tests locally, we can skip tests that would need To answer your questions:
Regarding the split of big packets - it's just not feasible (it is possible though). If you have a use case, where something larger than 16MB is stored in MySQL, you will need support for MySQL split packet functionality to fetch/update it without changing your application logic. If this packet_splitting merges, I'd love to contribute also: |
Or is it an option to raise the server configuration inside this functional test only, so we don't have to change the whole configuration? |
Hello @SimonFrings , Yes, that's a great idea. We could use It is also possible to skip those tests if current Let me know what you think. |
Maybe let's try this first, seems to be the best solution so far, we'll see if it works out. If not we can always come back to our other suggestions. 👍 |
Hello @SimonFrings , Test is now successful, but packet split is not tested as it does not have permission to increase the To increase Example from
Don't know if I can change the |
@dmarkic Seems a bit random as it would increase |
@SimonFrings , not really. I tried, but user does not have permission to change |
@dmarkic ok, to keep this going let's change the server settings for now to make this work, maybe we can find a better solution afterwards. |
@SimonFrings , I guess it's all working now with this small change. |
.github/workflows/ci.yml
Outdated
@@ -29,7 +29,7 @@ jobs: | |||
php-version: ${{ matrix.php }} | |||
coverage: xdebug | |||
- run: composer install | |||
- run: docker run -d --name mysql --net=host -e MYSQL_RANDOM_ROOT_PASSWORD=yes -e MYSQL_DATABASE=test -e MYSQL_USER=test -e MYSQL_PASSWORD=test mysql:5 | |||
- run: docker run -d --name mysql --net=host -e MYSQL_RANDOM_ROOT_PASSWORD=yes -e MYSQL_DATABASE=test -e MYSQL_USER=test -e MYSQL_PASSWORD=test mysql:5 --max-allowed-packet=17825792 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Seems like a good idea, but I think it makes more sense to use something like this:
- run: docker run -d --name mysql --net=host -e MYSQL_RANDOM_ROOT_PASSWORD=yes -e MYSQL_DATABASE=test -e MYSQL_USER=test -e MYSQL_PASSWORD=test mysql:5 --max-allowed-packet=17825792 | |
- run: docker run -d --name mysql --net=host -e MYSQL_RANDOM_ROOT_PASSWORD=yes -e MYSQL_DATABASE=test -e MYSQL_USER=test -e MYSQL_PASSWORD=test mysql:5 --max-allowed-packet=20M |
Using 17825792
could seem a bit random for others ^^
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sure, I'll make this change
src/Io/Parser.php
Outdated
* If last part was exactly 0xffffff in size, we need to send an empty packet to signal end | ||
* of packet splitting. | ||
*/ | ||
if (\strlen($packet) == 0 && $part_len == 0xffffff) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would it make sense to use ===
instead of ==
here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'll review my code and make changes
@SimonFrings , I guess that solves the whole PR with packet split support? |
tests/ResultQueryTest.php
Outdated
protected function checkMaxAllowedPacket($connection, $min = 0x1100000) | ||
{ | ||
return $connection->query('SHOW VARIABLES LIKE \'max_allowed_packet\'')->then( | ||
function ($res) use ($min, $connection) { | ||
$current = $res->resultRows[0]['Value']; | ||
if ($current < $min) { | ||
return $connection->query('SET GLOBAL max_allowed_packet = ?', [$min])->otherwise( | ||
function (\Throwable $e) use ($min, $current) { | ||
throw new \Exception( | ||
'Cannot set max_allowed_packet to: ' . $min . ' ' . | ||
'current: ' . $current . ' error: ' . $e->getMessage() | ||
); | ||
} | ||
); | ||
} | ||
return \React\Promise\resolve(); | ||
} | ||
)->then( | ||
function () { | ||
return true; | ||
} | ||
); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You said in one of your last comments:
I tried, but user does not have permission to change max_allowed_packet
In here you're trying to overwrite max_allowed_packet
, so I'm a little confused if it works or not. Or maybe this is a leftover? It seems like your tests will never overwrite max_allowed_packet
in this case because $min
never surpasses $current
, right?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, it will try. Maybe useful for someone with smaller max_allowed_packet
but permission to change it. If it does not succeed it will mark test as incomplete.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree that this is important for someone who wants to execute the tests and has a smaller max_allowed_packet
. We also recommend a temporary mysql
docker image to execute tests inside our documentation which doesn't involve raising max_allowed_packet
, so it will use the standard 16MiB :
For example, to create an empty test database, you can also use a temporary
mysql
Docker image like this:docker run -it --rm --net=host \ -e MYSQL_RANDOM_ROOT_PASSWORD=yes -e MYSQL_DATABASE=test \ -e MYSQL_USER=test -e MYSQL_PASSWORD=test mysql:5
Over all I am not sure if we should even be able to SET GLOBAL max_allowed_packet
as it changes the configuration of a database, which could be used by more than just one person. Changing someone else's gloabl configuration just feels wrong and I can imagine why it shouldn't be allowed. If we want to alter the max_allowed_packet
, we should do this temporary on the connection if possible.
As an alternative we could also update our temporary Docker image inside the Readme.md
like you already did inside the ci.yml
(same docker images btw) and if someone would use a database with a smaller max_allowed_packet
we should skip the necessary tests (not mark them incomplete)
What do you think?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, I agree, we should probably not set GLOBAL configuration. I'll rewrite tests to simply check if max_allowed_packet
is set to large enough value for tests, otherwise tests will be marked as incomplete.
I will update the Readme.md
file to include max_allowed_packet
setting.
…cket is too low. Add --max-allowed-packet=20M to README.md docker instructions
Sorry for late response. I've fixed the discussed issues.
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for updating the PR, we're getting closer and closer 👍
I added a few remarks below, interested what you think about them.
$this->debug('Waiting for complete packet with ' . $len . '/' . $this->pctSize . ' bytes'); | ||
|
||
return; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we should leave this blank line here, improves the readability and keeps the changelog a bit smaller
function (\Throwable $e) { | ||
$this->markTestIncomplete('checkMaxAllowedPacket: ' . $e->getMessage()); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we should skip these tests here instead of marking them as incomplete. As the documentation of PHPUnit says to mark tests as incomplete:
PHPUnit\Framework\IncompleteTest
is a marker interface for marking an exception that is raised by a test method as the result of the test being incomplete or currently not implemented.
The problem here isn't that some implementation is missing, it's more of a "Your configuration doesn't fit our test" case here. This fits the example for skipping tests with PHPUnit as described inside the docs:
In the test case class’ setUp() template method we check whether the MySQLi extension is available and use the markTestSkipped() method to skip the test if it is not.
Makes more sense, what do you think?
|
||
$promise = $this->checkMaxAllowedPacket($connection); | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think this test behaves the way you intended to. As I see it, this test will be marked as skipped if someone uses a max_allowed_packet
size of 16 MiB which should be allowed in this case. This is caused by the $min
default of your checkMaxAllowedPacket()
function, as it will set the minimum to 17MiB and throws an exception for configurations with 16 MiB.
)->otherwise( | ||
function (\Throwable $e) { | ||
$this->markTestIncomplete('checkMaxAllowedPacket: ' . $e->getMessage()); | ||
} | ||
)->always( | ||
function () use ($connection) { | ||
$connection->quit(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The PromiseInterface::always()
and PromiseInterface::otherwise()
functions are marked as deprecated as described in reactphp/promise, I don't think it's a good idea to use them in this context as we want to avoid using deprecated functions.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This only works on react-promise:3.x ... not react-promise:2.7.
Will only react-promise:3.x be supported? In current package.json both are supported.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You're right that both versions are supported and we already know that v3 will be the way forward. I agree that it should be compatible with both versions at this point, but looking into the future here. If we already know that this functionality will be removed at a later point in time, it makes sense to avoid using these functions, so we don't have to remove them again.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, but 3.x introduced that, 2.7 does not work with catch
and finally
. So, when 2.7 version is removed from this package, it will be time to also change those methods.
Hey @dmarkic, this PR is open for a long time now and I wanted to see how it's going 👍 I still think this is a valuable addition to the project, so what can we do to get this to a mergable state. A lot has happened since February 2023 and the first thing to be done here is to rebase and resolve the latest conflicts. Also taken from my last response, there are still some changes that should be done in here (e.g. avoid using the deprecated I want to avoid having pull requests and issues open for too long, so it's also okay to close this if there's currently no time to work on this topic and revisit this at a later point again. Interested in your thoughts! |
Hello @SimonFrings, I'll create new PR for 0.7.x version, since a lot has changed. You can close this one and I'll create a new one also with promise v3 support. I'll do this within few days. |
Alright, then I'll close this one 👍 |
This PR adds support for protocol packages larger than 16MiB, both receiving and sending such packets.