-
-
Notifications
You must be signed in to change notification settings - Fork 9.6k
[HttpFoundation] enhance PdoSessionHandler #10931
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
Conversation
I suspect your meant to open the PR against 2.3 |
No since it's a bc break. |
@Tobion But you have a bunch of unrelated commits in this PR. |
Because 2.3 is not merged in master yet. |
@Tobion 2.3 has now been merged into master. |
*/ | ||
private $pdo; | ||
|
||
/** | ||
* @var string|null|false DNS string or null for session.save_path or false when lazy connection disabled |
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.
Maybe you meant DSN
instead of DNS
?
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.
haha true
This would be ready. Where should I document the change (depends for which version it gets merged)? |
@@ -51,17 +51,68 @@ public function testInexistentTable() | |||
$storage->close(); | |||
} | |||
|
|||
public function testReadWriteRead() | |||
public function testWithLazyDnsConnection() |
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.
dsn*
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.
thx fixed
@Tobion I think similar changes should be done in the DBAL session handler. |
@stof you know what fabbot.io is complaining about? http://fabbot.io/report/symfony/symfony/10931/953226bdb666880c61157a42d7839eb42c9c8c9c |
There are some extra spaces after the ';' |
@venyii Thanks for spotting |
Reopened against 2.5: #10991 |
Ok, master 2.6 again. |
@fabpot ready |
I'll write a blog post about this soon. |
@Tobion please cross link your post here |
* @throws \PDOException When the table already exists | ||
* @throws \DomainException When an unsupported PDO driver is used | ||
*/ | ||
public function createTable() |
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 need to add a check that we are not in a transaction, because it would implicit commit it and thus ruin the locking when called after read(). http://dev.mysql.com/doc/refman/5.0/en/implicit-commit.html
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.
Hm doesn't really matter since as read() would raise an error anyway when the table doesn't exist. But to be correct rollback should be called.
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.
Done.
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.
FYI, PostgreSQL actually supports performing DDL changes in a transaction, and rolling back these changes a whole. Its one of the many reasons why I love PostgreSQL ;)
So if you create a table inside a transaction the table will not be visible outside of the transaction, but the pg_class catalogs tables (and related) are however locked until the transaction is released.
Not a problem in this case, just some useful information :)
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 the information
9ca183f
to
c900de5
Compare
@fabpot this is ready for merge. I tested the binary data and the different locking strategies with both mysql and sqlite. It works like a charm. But I don't have postgres, oracle, mssql available. So it would be good if the community can test the class with these DBMS. Or I might find time to set it up later. |
@symfony/deciders ping |
@Tobion should one of the locking strategies be recommended for the "regular-user"? |
The default strategy (transactional) is recommended because it's most reliable across dbms. |
The documentation could enjoy an update in this section: http://symfony.com/doc/current/cookbook/configuration/pdo_session_storage.html#sharing-your-database-connection-information As it's not recommended anymore to share the doctrine connection, we should deprecate this part |
What about an auto-save parameter in the framework bundle (in the session section)? |
@romainneutron this section is only about sharing the connection settings. Not the connection itself. So this part is still ok. |
woops, you're right |
I think there are different cases that need to be examined, e.g. an AJAX request to:
|
Maybe we could add a listener on |
What about some sort of debugging mechanism (e.g. A class using ArrayAccess) which will throw exceptions in case a code snippet tries to add/manipulate session data after the session has already been written+closed? |
Any votes? |
* PdoSessionHandler changes | ||
- implemented different session locking strategies to prevent loss of data by concurrent access to the same session | ||
- save session data in a binary column without base64_encode | ||
- added lifetime column to the session table which allows to have different lifetimes for each session |
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 should prefix this with [BC BREAK]
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.
done
👍 |
c900de5
to
1bc6680
Compare
I just realize this also fixes #9029 |
Thank you so much for working on this @Tobion. This is much appreciated. |
This PR was merged into the 2.6-dev branch. Discussion ---------- [HttpFoundation] enhance PdoSessionHandler | Q | A | ------------- | --- | Bug fix? | yes | New feature? | yes | BC breaks? | yes | Deprecations? | no | Tests pass? | yes | Fixed tickets | #5483, #2067, #2382, #9029 | License | MIT 0. [x] Continuation of locking implementation (#10908): Implement different locking strategies - `PdoSessionHandler::LOCK_TRANSACTIONAL` (default): Issues a real row lock but requires a transaction - `PdoSessionHandler::LOCK_ADVISORY`: app-level lock, safe as long as only the PdoSessionHandler accesses sessions, advantage is it does not require a transaction (not implemented for oracle or sqlsrv yet) - `PdoSessionHandler::LOCK_NONE`: basically what is was before, prone to race conditions, means the last session write wins 1. [x] Save session data as binary: Encoding session data was definitely the wrong solution. Session data is binary text (esp. when using other session.serialize_handler) that must stay as-is and thus must also be safed in a binary column. Base64 encoding session data just decreses performance and increases storage costs and is semantically wrong because it does not have a character encoding. That saving null bytes in Posgres won't work on a character column is also documented > First, binary strings specifically allow storing octets of value zero and other "non-printable" octets (usually, octets outside the range 32 to 126). Character strings disallow zero octets, and also disallow any other octet values and sequences of octet values that are invalid according to the database's selected character set encoding. http://www.postgresql.org/docs/9.1/static/datatype-binary.html#DATATYPE-BINARY-TABLE 2. [x] Implement lazy connections that are only opened when session is used by either passing a dsn string explicitly or falling back to session.save_path ini setting. Fixes #9029 3. [x] add a create table method that creates the correct table depending on database vendor. This makes the class self-documenting and standalone useable. 5. [x] add lifetime column to session table which allows to have different lifetimes for each session 6. [x] add isSessionExpired() method to be able to distinguish between a new session and one that expired due to inactivity, e.g. to display flash message to user 7. [x] added upgrade and changelog notes Commits ------- 1bc6680 [HttpFoundation] implement different locking strategies for sessions 6f5748e adjust sqlite table definition 5978fcf added upgrade and changelog notes for PdoSessionHandler 182a5d3 [HttpFoundation] add create table method to pdo session handler e79229d [HttpFoundation] allow different lifetime per session af1bb1f add test for null byte in session data 251238d [HttpFoundation] implement lazy connect for pdo session handler 7dad54c [HttpFoundation] remove base64 encoding of session data
return ''; | ||
} | ||
|
||
return $sessionRows[0][0]; |
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 guess it will not work at least for PostgreSQL, as the bytea
fields are returned as streams for this driver: http://php.net/manual/en/ref.pdo-pgsql.connection.php
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.
Good catch, I know Travis-ci supports both PostgreSQL and MySQL so maybe we can add an general integration test to make sure all the drivers are working properly?
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.
Ping @Tobion
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.
@rybakit As I said, I only tested it with mysql and sqlite. I would appreciate if you can test it with postgres and provide a fix.
A unit test with postgres would help for the simple case. But for the locking behavior we would need functional tests with simultaneous connections which is not that easy to do.
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.
@Tobion I've submitted a fix #12146 with simple unit tests. But I agree that to test it properly we need functional tests for each driver.
Btw, did you consider to split PdoSessionHandler
into SqlitePdoSessionHandler
, MysqlPdoSessionHandler
etc. (or create adapters) to ease tuning and remove all those if ('sqlite|mysql|...' === $this->driver)
checks?
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 I considered that. But then we would probably need to an abstract implementation with all the shared code and also change the pdoeessionhandler to be some sort of proxy, because you can still pass a pdo instance and then need to determine which concrete handler to use. Not sure if that makes things easier.
…with streams (rybakit) This PR was squashed before being merged into the 2.6-dev branch (closes #12146). Discussion ---------- [HttpFoundation] Fix PdoSessionHandler to work properly with streams | Q | A | ------------- | --- | Bug fix? | yes | New feature? | no | BC breaks? | no | Deprecations? | no | Tests pass? | yes | License | MIT Ref: #10931 (comment) Commits ------- 9531a2b [HttpFoundation] Fix PdoSessionHandler to work properly with streams
@Tobion hey, i just wanted to come back at you about this and thank you for implementing our suggestions. It helped us immensely and made it possible to stay up to date with the latest symfony versions. Especially exposing the lock strategies made it possible that we can utilize our session implementation. So, just a quick thank you for your work and for listening and seeking a constructive discussion (see #10908) (I know, it has been a while, but as I just updated our system to be based on symfony 2.7 I realized just how much this change helped) |
Continuation of locking implementation ([HttpFoundation] implement session locking for PDO #10908): Implement different locking strategies
PdoSessionHandler::LOCK_TRANSACTIONAL
(default): Issues a real row lock but requires a transactionPdoSessionHandler::LOCK_ADVISORY
: app-level lock, safe as long as only the PdoSessionHandler accesses sessions, advantage is it does not require a transaction (not implemented for oracle or sqlsrv yet)PdoSessionHandler::LOCK_NONE
: basically what is was before, prone to race conditions, means the last session write winsSave session data as binary: Encoding session data was definitely the wrong solution. Session data is binary text (esp. when using other session.serialize_handler) that must stay as-is and thus must also be safed in a binary column. Base64 encoding session data just decreses performance and increases storage costs and is semantically wrong because it does not have a character encoding.
That saving null bytes in Posgres won't work on a character column is also documented
Implement lazy connections that are only opened when session is used by either passing a dsn string explicitly or falling back to session.save_path ini setting. Fixes [HttpFoundation] PdoSessionHandler should connect to database only if session required #9029
add a create table method that creates the correct table depending on database vendor. This makes the class self-documenting and standalone useable.
add lifetime column to session table which allows to have different lifetimes for each session
add isSessionExpired() method to be able to distinguish between a new session and one that expired due to inactivity, e.g. to display flash message to user
added upgrade and changelog notes