-
Notifications
You must be signed in to change notification settings - Fork 18
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
Upgrade to async-session v4 #50
base: main
Are you sure you want to change the base?
Conversation
Thanks for this. I'd love to merge the upstream changes asap so we can fix the session extractor but at the same time for that to be useful the other crates that use it will also need to be updated. I've also not had much luck contacting the maintainers recently. |
Another important point here: we should retire the session extractor pair and instead use the session directly. |
Yeah, it is unfortunate. But he's still active generally speaking so hopefully things will fall into place soon
Ah, I was wondering about this. |
My hope is that we can avoid the |
Previously, we used to insert the session directly. So perhaps we could simply go back to this and avoid the handle. |
I've had a go at getting rid of I agree that the But extensions are not automatically propagated from That being said, |
Thanks for doing that. I agree with you assessment. I haven't looked closely at the |
I think something like this could work: diff --git a/src/extractors.rs b/src/extractors.rs
index 9b9a547..85fe0ca 100644
--- a/src/extractors.rs
+++ b/src/extractors.rs
@@ -3,78 +3,46 @@
use std::ops::{Deref, DerefMut};
use axum::{async_trait, extract::FromRequestParts, http::request::Parts, Extension};
-use tokio::sync::{OwnedRwLockReadGuard, OwnedRwLockWriteGuard};
+use tokio::sync::OwnedMutexGuard;
use crate::SessionHandle;
/// An extractor which provides a readable session. Sessions may have many
/// readers.
#[derive(Debug)]
-pub struct ReadableSession {
- session: OwnedRwLockReadGuard<async_session::Session>,
+pub struct Session {
+ session_guard: OwnedMutexGuard<async_session::Session>,
}
-impl Deref for ReadableSession {
- type Target = OwnedRwLockReadGuard<async_session::Session>;
+impl Deref for Session {
+ type Target = OwnedMutexGuard<async_session::Session>;
fn deref(&self) -> &Self::Target {
- &self.session
+ &self.session_guard
}
}
-#[async_trait]
-impl<S> FromRequestParts<S> for ReadableSession
-where
- S: Send + Sync,
-{
- type Rejection = std::convert::Infallible;
-
- async fn from_request_parts(parts: &mut Parts, state: &S) -> Result<Self, Self::Rejection> {
- let Extension(session_handle): Extension<SessionHandle> =
- Extension::from_request_parts(parts, state)
- .await
- .expect("Session extension missing. Is the session layer installed?");
- let session = session_handle.read_owned().await;
-
- Ok(Self { session })
- }
-}
-
-/// An extractor which provides a writable session. Sessions may have only one
-/// writer.
-#[derive(Debug)]
-pub struct WritableSession {
- session: OwnedRwLockWriteGuard<async_session::Session>,
-}
-
-impl Deref for WritableSession {
- type Target = OwnedRwLockWriteGuard<async_session::Session>;
-
- fn deref(&self) -> &Self::Target {
- &self.session
- }
-}
-
-impl DerefMut for WritableSession {
+impl DerefMut for Session {
fn deref_mut(&mut self) -> &mut Self::Target {
- &mut self.session
+ &mut self.session_guard
}
}
#[async_trait]
-impl<S> FromRequestParts<S> for WritableSession
+impl<S> FromRequestParts<S> for Session
where
- S: Send + Sync,
+ S: Send + Sync + Clone,
{
type Rejection = std::convert::Infallible;
- async fn from_request_parts(parts: &mut Parts, state: &S) -> Result<Self, Self::Rejection> {
- let Extension(session_handle): Extension<SessionHandle> =
- Extension::from_request_parts(parts, state)
- .await
- .expect("Session extension missing. Is the session layer installed?");
- let session = session_handle.write_owned().await;
+ async fn from_request_parts(parts: &mut Parts, _state: &S) -> Result<Self, Self::Rejection> {
+ use axum::RequestPartsExt;
+ let Extension(session) = parts
+ .extract::<Extension<SessionHandle>>()
+ .await
+ .expect("Session extension missing. Is the session layer installed?");
- Ok(Self { session })
+ let session_guard = session.lock_owned().await;
+ Ok(Self { session_guard })
}
}
diff --git a/src/session.rs b/src/session.rs
index 0f632d9..6947bfb 100644
--- a/src/session.rs
+++ b/src/session.rs
@@ -20,7 +20,7 @@ use base64::{engine::general_purpose::STANDARD as BASE64, Engine};
use futures::future::BoxFuture;
use hmac::{Hmac, Mac};
use sha2::{digest::generic_array::GenericArray, Sha256};
-use tokio::sync::RwLock;
+use tokio::sync::Mutex;
use tower::{Layer, Service};
const BASE64_DIGEST_LEN: usize = 44;
@@ -34,7 +34,7 @@ const BASE64_DIGEST_LEN: usize = 44;
/// than using the handle directly. A notable exception is when using this
/// library as a generic Tower middleware: such use cases will consume the
/// handle directly.
-pub type SessionHandle = Arc<RwLock<async_session::Session>>;
+pub type SessionHandle = Arc<Mutex<async_session::Session>>;
/// Controls how the session data is persisted and created.
#[derive(Clone)]
@@ -189,7 +189,7 @@ where
None => None,
};
- Arc::new(RwLock::new(
+ Arc::new(Mutex::new(
session
.and_then(async_session::Session::validate)
.unwrap_or_default(),
@@ -331,22 +331,21 @@ where
let mut inner = std::mem::replace(&mut self.inner, inner);
Box::pin(async move {
let session_handle = session_layer.load_or_create(cookie_value.clone()).await;
+ let mut session = session_handle.lock().await;
- let mut session = session_handle.write().await;
if let Some(ttl) = session_layer.session_ttl {
(*session).expire_in(ttl);
}
drop(session);
request.extensions_mut().insert(session_handle.clone());
+
let mut response = inner.call(request).await?;
- let session = session_handle.read().await;
+ let mut session = session_handle.lock().await;
let (session_is_destroyed, session_data_changed) =
(session.is_destroyed(), session.data_changed());
- drop(session);
- let mut session = session_handle.write().await;
if session_is_destroyed {
if let Err(e) = session_layer.store.destroy_session(&mut session).await {
tracing::error!("Failed to destroy session: {:?}", e); |
There's a build failing which I think is related to the sqlx example. I'm maintaining a separate fork of that repo, so if we'd like to swap that in that should fix the build. |
This is I think completely orthogonal to |
Co-authored-by: Max Countryman <[email protected]>
That's fair, but I think it's a session store concern, be it in the implementation or in the API? But I don't know how worrying it is in the context of browser sessions. |
In It's probably okay to punt that responsibility elsewhere. That said, I think we should at least document it as a foot gun even if most applications may not care about this. (I also wonder if we can work around this by plucking out the |
Hi folks, I started a discussion related to the direction we should take The goal with this is to improve the There's a couple of folks who would like the session concept to be replaced with custom types, but I'm skeptical that's a good fit for a crate that's intended to evolve and replace this crate. However, I would love your feedback more generally about |
When will the bug fix release be available |
@headironc its difficult to say precisely since this is dependent on async-session merging and releasing a PR that's been open for some time. For now, you can use this directly via git. At the same time, I've also started working on tower-sessions, which could circumvent these issues altogether. Feedback appreciated. |
oh, thanks much |
I still have this problem This is dependencies, i use this Replace RwLock with Mutex
This is my middleware
This is my handler.
Still block request This worked
|
Sorry you're running into this. Something is holding the lock, which unfortunately is a clear drawback of how axum-sessions is designed today. Whether we use Mutex or RwLock, in either case we're using a lock so that doesn't change the fundamental issue with locking as we are. For that reason, I took a different approach with tower-sessions: we still need a Mutex or RwLock, because without this we can't have shared mutable state. However, we can defer locking until we actually invoke some operation over the underlying hash map. This is how tower-sessions works. Deadlocks should be far less likely with this approach. (Altho there is still a lock: that said, this is how other tower middleware, like tower-cookies, work so I have some confidence it's a pattern that should work well in practice.) I'm close to publishing an initial release of tower-sessions and if you'd like to try that with your use case it would be helpful to know this new approach does address this problem. |
I'm looking forward to the new library |
@headironc I've released v0.1.0 if you'd like to try. |
Replaces #29
This PR is mostly for people who have encountered issues such as #32 and need a temporary fix. It seems to work fine in my case.