mirror of
https://github.com/servo/servo.git
synced 2025-08-03 04:30:10 +01:00
http-cache: wait on pending stores
and various small improvements
This commit is contained in:
parent
e05f0b41ba
commit
4f3ba70704
4 changed files with 239 additions and 92 deletions
|
@ -12,7 +12,7 @@ use crate::fetch::methods::{
|
|||
};
|
||||
use crate::fetch::methods::{Data, DoneChannel, FetchContext, Target};
|
||||
use crate::hsts::HstsList;
|
||||
use crate::http_cache::HttpCache;
|
||||
use crate::http_cache::{CacheKey, HttpCache};
|
||||
use crate::resource_thread::AuthCache;
|
||||
use crossbeam_channel::{unbounded, Sender};
|
||||
use devtools_traits::{
|
||||
|
@ -53,7 +53,7 @@ use std::iter::FromIterator;
|
|||
use std::mem;
|
||||
use std::ops::Deref;
|
||||
use std::str::FromStr;
|
||||
use std::sync::{Mutex, RwLock};
|
||||
use std::sync::{Condvar, Mutex, RwLock};
|
||||
use std::time::{Duration, SystemTime};
|
||||
use time::{self, Tm};
|
||||
use tokio::prelude::{future, Future, Stream};
|
||||
|
@ -63,10 +63,25 @@ lazy_static! {
|
|||
pub static ref HANDLE: Mutex<Runtime> = { Mutex::new(Runtime::new().unwrap()) };
|
||||
}
|
||||
|
||||
/// The various states an entry of the HttpCache can be in.
|
||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||
pub enum HttpCacheEntryState {
|
||||
/// The entry is fully up-to-date,
|
||||
/// there are no pending concurrent stores,
|
||||
/// and it is ready to construct cached responses.
|
||||
ReadyToConstruct,
|
||||
/// The entry is pending a number of concurrent stores.
|
||||
PendingStore(usize),
|
||||
}
|
||||
|
||||
pub struct HttpState {
|
||||
pub hsts_list: RwLock<HstsList>,
|
||||
pub cookie_jar: RwLock<CookieStorage>,
|
||||
pub http_cache: RwLock<HttpCache>,
|
||||
/// A map of cache key to entry state,
|
||||
/// reflecting whether the cache entry is ready to read from,
|
||||
/// or whether a concurrent pending store should be awaited.
|
||||
pub http_cache_state: Mutex<HashMap<CacheKey, Arc<(Mutex<HttpCacheEntryState>, Condvar)>>>,
|
||||
pub auth_cache: RwLock<AuthCache>,
|
||||
pub history_states: RwLock<HashMap<HistoryStateId, Vec<u8>>>,
|
||||
pub client: Client<Connector, Body>,
|
||||
|
@ -80,6 +95,7 @@ impl HttpState {
|
|||
auth_cache: RwLock::new(AuthCache::new()),
|
||||
history_states: RwLock::new(HashMap::new()),
|
||||
http_cache: RwLock::new(HttpCache::new()),
|
||||
http_cache_state: Mutex::new(HashMap::new()),
|
||||
client: create_http_client(ssl_connector_builder, HANDLE.lock().unwrap().executor()),
|
||||
}
|
||||
}
|
||||
|
@ -1020,42 +1036,124 @@ fn http_network_or_cache_fetch(
|
|||
// Step 5.18
|
||||
// TODO If there’s a proxy-authentication entry, use it as appropriate.
|
||||
|
||||
// Step 5.19
|
||||
if let Ok(http_cache) = context.state.http_cache.read() {
|
||||
if let Some(response_from_cache) = http_cache.construct_response(&http_request, done_chan) {
|
||||
let response_headers = response_from_cache.response.headers.clone();
|
||||
// Substep 1, 2, 3, 4
|
||||
let (cached_response, needs_revalidation) =
|
||||
match (http_request.cache_mode, &http_request.mode) {
|
||||
(CacheMode::ForceCache, _) => (Some(response_from_cache.response), false),
|
||||
(CacheMode::OnlyIfCached, &RequestMode::SameOrigin) => {
|
||||
(Some(response_from_cache.response), false)
|
||||
},
|
||||
(CacheMode::OnlyIfCached, _) |
|
||||
(CacheMode::NoStore, _) |
|
||||
(CacheMode::Reload, _) => (None, false),
|
||||
(_, _) => (
|
||||
Some(response_from_cache.response),
|
||||
response_from_cache.needs_validation,
|
||||
),
|
||||
};
|
||||
if needs_revalidation {
|
||||
revalidating_flag = true;
|
||||
// Substep 5
|
||||
if let Some(http_date) = response_headers.typed_get::<LastModified>() {
|
||||
let http_date: SystemTime = http_date.into();
|
||||
http_request
|
||||
.headers
|
||||
.typed_insert(IfModifiedSince::from(http_date));
|
||||
// If the cache is not ready to construct a response, wait.
|
||||
//
|
||||
// The cache is not ready if a previous fetch checked the cache, found nothing,
|
||||
// and moved on to a network fetch, and hasn't updated the cache yet with a pending resource.
|
||||
//
|
||||
// Note that this is a different workflow from the one involving `wait_for_cached_response`.
|
||||
// That one happens when a fetch gets a cache hit, and the resource is pending completion from the network.
|
||||
{
|
||||
let (lock, cvar) = {
|
||||
let entry_key = CacheKey::new(&http_request);
|
||||
let mut state_map = context.state.http_cache_state.lock().unwrap();
|
||||
&*state_map
|
||||
.entry(entry_key)
|
||||
.or_insert_with(|| {
|
||||
Arc::new((
|
||||
Mutex::new(HttpCacheEntryState::ReadyToConstruct),
|
||||
Condvar::new(),
|
||||
))
|
||||
})
|
||||
.clone()
|
||||
};
|
||||
|
||||
// Start of critical section on http-cache state.
|
||||
let mut state = lock.lock().unwrap();
|
||||
while let HttpCacheEntryState::PendingStore(_) = *state {
|
||||
let (current_state, time_out) = cvar
|
||||
.wait_timeout(state, Duration::from_millis(500))
|
||||
.unwrap();
|
||||
state = current_state;
|
||||
if time_out.timed_out() {
|
||||
// After a timeout, ignore the pending store.
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Step 5.19
|
||||
if let Ok(http_cache) = context.state.http_cache.read() {
|
||||
if let Some(response_from_cache) =
|
||||
http_cache.construct_response(&http_request, done_chan)
|
||||
{
|
||||
let response_headers = response_from_cache.response.headers.clone();
|
||||
// Substep 1, 2, 3, 4
|
||||
let (cached_response, needs_revalidation) =
|
||||
match (http_request.cache_mode, &http_request.mode) {
|
||||
(CacheMode::ForceCache, _) => (Some(response_from_cache.response), false),
|
||||
(CacheMode::OnlyIfCached, &RequestMode::SameOrigin) => {
|
||||
(Some(response_from_cache.response), false)
|
||||
},
|
||||
(CacheMode::OnlyIfCached, _) |
|
||||
(CacheMode::NoStore, _) |
|
||||
(CacheMode::Reload, _) => (None, false),
|
||||
(_, _) => (
|
||||
Some(response_from_cache.response),
|
||||
response_from_cache.needs_validation,
|
||||
),
|
||||
};
|
||||
if cached_response.is_none() {
|
||||
// Ensure the done chan is not set if we're not using the cached response,
|
||||
// as the cache might have set it to Some if it constructed a pending response.
|
||||
*done_chan = None;
|
||||
|
||||
// Update the cache state, incrementing the pending store count,
|
||||
// or starting the count.
|
||||
if let HttpCacheEntryState::PendingStore(i) = *state {
|
||||
let new = i + 1;
|
||||
*state = HttpCacheEntryState::PendingStore(new);
|
||||
} else {
|
||||
*state = HttpCacheEntryState::PendingStore(1);
|
||||
}
|
||||
}
|
||||
if let Some(entity_tag) = response_headers.get(header::ETAG) {
|
||||
http_request
|
||||
.headers
|
||||
.insert(header::IF_NONE_MATCH, entity_tag.clone());
|
||||
if needs_revalidation {
|
||||
revalidating_flag = true;
|
||||
// Substep 5
|
||||
if let Some(http_date) = response_headers.typed_get::<LastModified>() {
|
||||
let http_date: SystemTime = http_date.into();
|
||||
http_request
|
||||
.headers
|
||||
.typed_insert(IfModifiedSince::from(http_date));
|
||||
}
|
||||
if let Some(entity_tag) = response_headers.get(header::ETAG) {
|
||||
http_request
|
||||
.headers
|
||||
.insert(header::IF_NONE_MATCH, entity_tag.clone());
|
||||
}
|
||||
} else {
|
||||
// Substep 6
|
||||
response = cached_response;
|
||||
}
|
||||
}
|
||||
}
|
||||
// Notify the next thread waiting in line, if there is any.
|
||||
if *state == HttpCacheEntryState::ReadyToConstruct {
|
||||
cvar.notify_one();
|
||||
}
|
||||
// End of critical section on http-cache state.
|
||||
}
|
||||
|
||||
// Decrement the number of pending stores,
|
||||
// and set the state to ready to construct,
|
||||
// if no stores are pending.
|
||||
fn update_http_cache_state(context: &FetchContext, http_request: &Request) {
|
||||
let (lock, cvar) = {
|
||||
let entry_key = CacheKey::new(&http_request);
|
||||
let mut state_map = context.state.http_cache_state.lock().unwrap();
|
||||
&*state_map
|
||||
.get_mut(&entry_key)
|
||||
.expect("Entry in http-cache state to have been previously inserted")
|
||||
.clone()
|
||||
};
|
||||
let mut state = lock.lock().unwrap();
|
||||
if let HttpCacheEntryState::PendingStore(i) = *state {
|
||||
let new = i - 1;
|
||||
if new == 0 {
|
||||
*state = HttpCacheEntryState::ReadyToConstruct;
|
||||
// Notify the next thread waiting in line, if there is any.
|
||||
cvar.notify_one();
|
||||
} else {
|
||||
// Substep 6
|
||||
response = cached_response;
|
||||
*state = HttpCacheEntryState::PendingStore(new);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1065,6 +1163,7 @@ fn http_network_or_cache_fetch(
|
|||
// The cache constructed a response with a body of ResponseBody::Receiving.
|
||||
// We wait for the response in the cache to "finish",
|
||||
// with a body of either Done or Cancelled.
|
||||
assert!(response.is_some());
|
||||
loop {
|
||||
match ch
|
||||
.1
|
||||
|
@ -1095,6 +1194,9 @@ fn http_network_or_cache_fetch(
|
|||
if response.is_none() {
|
||||
// Substep 1
|
||||
if http_request.cache_mode == CacheMode::OnlyIfCached {
|
||||
// The cache will not be updated,
|
||||
// set its state to ready to construct.
|
||||
update_http_cache_state(context, &http_request);
|
||||
return Response::network_error(NetworkError::Internal(
|
||||
"Couldn't find response in cache".into(),
|
||||
));
|
||||
|
@ -1141,6 +1243,9 @@ fn http_network_or_cache_fetch(
|
|||
|
||||
let mut response = response.unwrap();
|
||||
|
||||
// The cache has been updated, set its state to ready to construct.
|
||||
update_http_cache_state(context, &http_request);
|
||||
|
||||
// Step 8
|
||||
// TODO: if necessary set response's range-requested flag
|
||||
|
||||
|
@ -1170,6 +1275,10 @@ fn http_network_or_cache_fetch(
|
|||
return response;
|
||||
}
|
||||
|
||||
// Make sure this is set to None,
|
||||
// since we're about to start a new `http_network_or_cache_fetch`.
|
||||
*done_chan = None;
|
||||
|
||||
// Substep 4
|
||||
response = http_network_or_cache_fetch(
|
||||
http_request,
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue