Skip to content
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

Make resource requests cancelable (issue 4974) #5826

Closed
wants to merge 2 commits into from
Closed
Changes from 1 commit
Commits
File filter...
Filter file types
Jump to…
Jump to file
Failed to load files.

Always

Just for now

Next

Make resource requests cancelable

  • Loading branch information
marcusklaas committed Apr 24, 2015
commit 58d7e37bf53f0fb69b1bf22837e827915c6090bd
@@ -5,7 +5,7 @@
use net_traits::{LoadData, Metadata, LoadConsumer};
use net_traits::ProgressMsg::Done;
use mime_classifier::MIMEClassifier;
use resource_task::start_sending;
use resource_task::{start_sending, CancelationListener};
use file_loader;

use url::Url;
@@ -18,7 +18,7 @@ use std::borrow::IntoCow;
use std::fs::PathExt;
use std::sync::Arc;

pub fn factory(mut load_data: LoadData, start_chan: LoadConsumer, classifier: Arc<MIMEClassifier>) {
pub fn factory(mut load_data: LoadData, start_chan: LoadConsumer, classifier: Arc<MIMEClassifier>, cancel_receiver: CancelationListener) {
match load_data.url.non_relative_scheme_data().unwrap() {
"blank" => {
let chan = start_sending(start_chan, Metadata {
@@ -44,5 +44,5 @@ pub fn factory(mut load_data: LoadData, start_chan: LoadConsumer, classifier: Ar
return
}
};
file_loader::factory(load_data, start_chan, classifier)
file_loader::factory(load_data, start_chan, classifier, cancel_receiver)
}
@@ -5,23 +5,23 @@
use net_traits::{LoadData, Metadata, LoadConsumer};
use net_traits::ProgressMsg::{Payload, Done};
use mime_classifier::MIMEClassifier;
use resource_task::start_sending;
use resource_task::{start_sending, CancelationListener};

use rustc_serialize::base64::FromBase64;

use hyper::mime::Mime;
use std::sync::Arc;
use url::{percent_decode, SchemeData};

pub fn factory(load_data: LoadData, senders: LoadConsumer, _classifier: Arc<MIMEClassifier>) {
pub fn factory(load_data: LoadData, senders: LoadConsumer, _classifier: Arc<MIMEClassifier>, cancel_receiver: CancelationListener) {
// NB: we don't spawn a new task.
// Hypothesis: data URLs are too small for parallel base64 etc. to be worth it.
// Should be tested at some point.
// Left in separate function to allow easy moving to a task, if desired.
load(load_data, senders)
load(load_data, senders, cancel_receiver)
}

pub fn load(load_data: LoadData, start_chan: LoadConsumer) {
pub fn load(load_data: LoadData, start_chan: LoadConsumer, cancel_receiver: CancelationListener) {
let url = load_data.url;
assert!(&*url.scheme == "data");

@@ -45,6 +45,12 @@ pub fn load(load_data: LoadData, start_chan: LoadConsumer) {
return;
}

// Check that the request has not been cancelled,
// even though it is very unlikely to happen this soon
if cancel_receiver.is_cancelled() {
return;
}

// ";base64" must come at the end of the content type, per RFC 2397.
// rust-http will fail to parse it because there's no =value part.
let mut is_base64 = false;
@@ -5,7 +5,7 @@
use net_traits::{LoadData, Metadata, LoadConsumer};
use net_traits::ProgressMsg::{Payload, Done};
use mime_classifier::MIMEClassifier;
use resource_task::{start_sending, start_sending_sniffed, ProgressSender};
use resource_task::{start_sending, start_sending_sniffed, ProgressSender, CancelationListener};

use std::borrow::ToOwned;
use std::fs::File;
@@ -43,7 +43,7 @@ fn read_all(reader: &mut File, progress_chan: &ProgressSender)
}
}

pub fn factory(load_data: LoadData, senders: LoadConsumer, classifier: Arc<MIMEClassifier>) {
pub fn factory(load_data: LoadData, senders: LoadConsumer, classifier: Arc<MIMEClassifier>, cancel_receiver: CancelationListener) {
let url = load_data.url;
assert!(&*url.scheme == "file");
spawn_named("file_loader".to_owned(), move || {
@@ -53,12 +53,19 @@ pub fn factory(load_data: LoadData, senders: LoadConsumer, classifier: Arc<MIMEC
Ok(file_path) => {
match File::open(&file_path) {
Ok(ref mut reader) => {
if cancel_receiver.is_cancelled() {
return;
}
let res = read_block(reader);
let (res, progress_chan) = match res {
Ok(ReadStatus::Partial(buf)) => {
let progress_chan = start_sending_sniffed(senders, metadata,
classifier, &buf);
progress_chan.send(Payload(buf)).unwrap();

if cancel_receiver.is_cancelled() {
return;
}
(read_all(reader, &progress_chan), progress_chan)
}
Ok(ReadStatus::EOF) | Err(_) =>
@@ -5,7 +5,7 @@
use net_traits::{ControlMsg, CookieSource, LoadData, Metadata, LoadConsumer};
use net_traits::ProgressMsg::{Payload, Done};
use mime_classifier::MIMEClassifier;
use resource_task::{start_sending_opt, start_sending_sniffed_opt};
use resource_task::{start_sending_opt, start_sending_sniffed_opt, CancelationListener};

use log;
use std::collections::HashSet;
@@ -32,9 +32,9 @@ use url::{Url, UrlParser};
use std::borrow::ToOwned;

pub fn factory(cookies_chan: Sender<ControlMsg>)
-> Box<Invoke<(LoadData, LoadConsumer, Arc<MIMEClassifier>)> + Send> {
box move |(load_data, senders, classifier)| {
spawn_named("http_loader".to_owned(), move || load(load_data, senders, classifier, cookies_chan))
-> Box<Invoke<(LoadData, LoadConsumer, Arc<MIMEClassifier>, CancelationListener)> + Send> {
box move |(load_data, senders, classifier, cancel_receiver)| {
spawn_named("http_loader".to_owned(), move || load(load_data, senders, classifier, cookies_chan, cancel_receiver))
}
}

@@ -66,7 +66,8 @@ fn read_block<R: Read>(reader: &mut R) -> Result<ReadResult, ()> {
}
}

fn load(mut load_data: LoadData, start_chan: LoadConsumer, classifier: Arc<MIMEClassifier>, cookies_chan: Sender<ControlMsg>) {
fn load(mut load_data: LoadData, start_chan: LoadConsumer, classifier: Arc<MIMEClassifier>,
cookies_chan: Sender<ControlMsg>, cancel_receiver: CancelationListener) {
// FIXME: At the time of writing this FIXME, servo didn't have any central
// location for configuration. If you're reading this and such a
// repository DOES exist, please update this constant to use it.
@@ -140,7 +141,7 @@ reason: \"certificate verify failed\" }]";
let mut image = resources_dir_path();
image.push("badcert.html");
let load_data = LoadData::new(Url::from_file_path(&*image).unwrap());
file_loader::factory(load_data, start_chan, classifier);
file_loader::factory(load_data, start_chan, classifier, cancel_receiver);
return;
},
Err(e) => {
@@ -186,6 +187,10 @@ reason: \"certificate verify failed\" }]";
info!("{:?}", load_data.data);
}

if cancel_receiver.is_cancelled() {
return;
}

// Avoid automatically sending request body if a redirect has occurred.
let writer = match load_data.data {
Some(ref data) if iters == 1 => {
@@ -318,6 +323,10 @@ reason: \"certificate verify failed\" }]";
}
}

if cancel_receiver.is_cancelled() {
return;
}

match encoding_str {
Some(encoding) => {
if encoding == "gzip" {
@@ -311,7 +311,7 @@ impl ImageCache {
url: url,
sender: self.progress_sender.clone(),
};
self.resource_task.send(ControlMsg::Load(load_data, LoadConsumer::Listener(listener))).unwrap();
self.resource_task.send(ControlMsg::Load(load_data, LoadConsumer::Listener(listener), None)).unwrap();
}
}
}
@@ -12,7 +12,7 @@ use cookie_storage::CookieStorage;
use cookie;
use mime_classifier::MIMEClassifier;

use net_traits::{ControlMsg, LoadData, LoadResponse, LoadConsumer};
use net_traits::{ControlMsg, LoadData, LoadResponse, LoadConsumer, ResourceId};
use net_traits::{Metadata, ProgressMsg, ResourceTask, AsyncResponseTarget, ResponseAction};
use net_traits::ProgressMsg::Done;
use util::opts;
@@ -29,7 +29,7 @@ use std::fs::File;
use std::io::{BufReader, Read};
use std::str::FromStr;
use std::sync::Arc;
use std::sync::mpsc::{channel, Receiver, Sender};
use std::sync::mpsc::{channel, Receiver, Sender, TryRecvError};
use std::thunk::Invoke;

static mut HOST_TABLE: Option<*mut HashMap<String, String>> = None;
@@ -180,12 +180,38 @@ pub fn replace_hosts(mut load_data: LoadData, host_table: *mut HashMap<String, S
return load_data;
}


pub struct CancelLoad;

pub struct CancelationListener(Option<Receiver<CancelLoad>>);

impl CancelationListener {
pub fn from_receiver(receiver: Option<Receiver<CancelLoad>>) -> CancelationListener {
CancelationListener(receiver)
}

pub fn is_cancelled(&self) -> bool {
match self.0 {
Some(ref receiver) => match receiver.try_recv() {
Ok(..) => true,
Err(e) => match e {
TryRecvError::Empty => false,
_ => true
}
},
None => false
}
}
}

struct ResourceManager {
from_client: Receiver<ControlMsg>,
user_agent: Option<String>,
cookie_storage: CookieStorage,
resource_task: Sender<ControlMsg>,
mime_classifier: Arc<MIMEClassifier>,
resource_id_map: HashMap<ResourceId, Sender<CancelLoad>>,
next_resource_id: ResourceId
}

impl ResourceManager {
@@ -197,17 +223,16 @@ impl ResourceManager {
cookie_storage: CookieStorage::new(),
resource_task: resource_task,
mime_classifier: Arc::new(MIMEClassifier::new()),
resource_id_map: HashMap::new(),
next_resource_id: ResourceId::new()
}
}
}


impl ResourceManager {
fn start(&mut self) {
loop {
match self.from_client.recv().unwrap() {
ControlMsg::Load(load_data, consumer) => {
self.load(load_data, consumer)
ControlMsg::Load(load_data, consumer, resource) => {
self.load(load_data, consumer, resource)
}
ControlMsg::SetCookiesForUrl(request, cookie_list, source) => {
let header = Header::parse_header(&[cookie_list.into_bytes()]);
@@ -222,14 +247,17 @@ impl ResourceManager {
ControlMsg::GetCookiesForUrl(url, consumer, source) => {
consumer.send(self.cookie_storage.cookies_for_url(&url, source)).unwrap();
}
ControlMsg::Cancel(resource_id) => {
self.resource_id_map.get(&resource_id).unwrap().send(CancelLoad).unwrap();
},
ControlMsg::Exit => {
break
}
}
}
}

fn load(&mut self, mut load_data: LoadData, consumer: LoadConsumer) {
fn load(&mut self, mut load_data: LoadData, consumer: LoadConsumer, resource_sender: Option<Sender<ResourceId>>) {
unsafe {
if let Some(host_table) = HOST_TABLE {
load_data = replace_hosts(load_data, host_table);
@@ -238,10 +266,10 @@ impl ResourceManager {

self.user_agent.as_ref().map(|ua| load_data.headers.set(UserAgent(ua.clone())));

fn from_factory(factory: fn(LoadData, LoadConsumer, Arc<MIMEClassifier>))
-> Box<Invoke<(LoadData, LoadConsumer, Arc<MIMEClassifier>)> + Send> {
box move |(load_data, senders, classifier)| {
factory(load_data, senders, classifier)
fn from_factory(factory: fn(LoadData, LoadConsumer, Arc<MIMEClassifier>, CancelationListener))
-> Box<Invoke<(LoadData, LoadConsumer, Arc<MIMEClassifier>, CancelationListener)> + Send> {
box move |(load_data, senders, classifier, cancel_receiver)| {
factory(load_data, senders, classifier, cancel_receiver)
}
}

@@ -259,6 +287,21 @@ impl ResourceManager {
};
debug!("resource_task: loading url: {}", load_data.url.serialize());

loader.invoke((load_data, consumer, self.mime_classifier.clone()));
let cancelation_receiver = match resource_sender {
Some(sender) => {
let resource_id = self.next_resource_id.increment();
let (cancel_sender, cancel_receiver) = channel();

self.resource_id_map.insert(resource_id, cancel_sender);
sender.send(resource_id).unwrap();

Some(cancel_receiver)
},
None => None
};

let cancelation_listener = CancelationListener::from_receiver(cancelation_receiver);

loader.invoke((load_data, consumer, self.mime_classifier.clone(), cancelation_listener));
}
}
@@ -109,16 +109,36 @@ pub enum LoadConsumer {
Listener(Box<AsyncResponseTarget + Send>),
}

/// Identifier for a resource
#[derive(PartialEq, Eq, Hash, Copy, Clone)]
pub struct ResourceId(u32);

impl ResourceId {
pub fn new() -> ResourceId {
ResourceId(0)
}

pub fn increment(&mut self) -> ResourceId {
let current = ResourceId(self.0);

self.0 += 1;

current
}
}

/// Handle to a resource task
pub type ResourceTask = Sender<ControlMsg>;

pub enum ControlMsg {
/// Request the data associated with a particular URL
Load(LoadData, LoadConsumer),
Load(LoadData, LoadConsumer, Option<Sender<ResourceId>>),
/// Store a set of cookies for a given originating URL
SetCookiesForUrl(Url, String, CookieSource),
/// Retrieve the stored cookies for a given URL
GetCookiesForUrl(Url, Sender<Option<String>>, CookieSource),
/// Cancel the request
Cancel(ResourceId),
Exit
}

@@ -176,16 +196,12 @@ impl Metadata {

/// Extract the parts of a Mime that we care about.
pub fn set_content_type(&mut self, content_type: Option<&Mime>) {
match content_type {
None => (),
Some(mime) => {
self.content_type = Some(ContentType(mime.clone()));
let &Mime(_, _, ref parameters) = mime;
for &(ref k, ref v) in parameters.iter() {
if &Attr::Charset == k {
self.charset = Some(v.to_string());
}
}
if let Some(mime) = content_type {
self.content_type = Some(ContentType(mime.clone()));
let &Mime(_, _, ref parameters) = mime;
let charset_pair = parameters.iter().find(|&&(ref key, _)| key == &Attr::Charset);
if let Some(&(_, ref value)) = charset_pair {
self.charset = Some(value.to_string());
}
}
}
@@ -213,7 +229,7 @@ pub enum ProgressMsg {
pub fn load_whole_resource(resource_task: &ResourceTask, url: Url)
-> Result<(Metadata, Vec<u8>), String> {
let (start_chan, start_port) = channel();
resource_task.send(ControlMsg::Load(LoadData::new(url), LoadConsumer::Channel(start_chan))).unwrap();
resource_task.send(ControlMsg::Load(LoadData::new(url), LoadConsumer::Channel(start_chan), None)).unwrap();
let response = start_port.recv().unwrap();

let mut buf = vec!();
@@ -229,7 +245,7 @@ pub fn load_whole_resource(resource_task: &ResourceTask, url: Url)
/// Load a URL asynchronously and iterate over chunks of bytes from the response.
pub fn load_bytes_iter(resource_task: &ResourceTask, url: Url) -> (Metadata, ProgressMsgPortIterator) {
let (input_chan, input_port) = channel();
resource_task.send(ControlMsg::Load(LoadData::new(url), LoadConsumer::Channel(input_chan))).unwrap();
resource_task.send(ControlMsg::Load(LoadData::new(url), LoadConsumer::Channel(input_chan), None)).unwrap();

let response = input_port.recv().unwrap();
let iter = ProgressMsgPortIterator { progress_port: response.progress_port };
@@ -255,5 +271,3 @@ impl Iterator for ProgressMsgPortIterator {
}
}
}


ProTip! Use n and p to navigate between commits in a pull request.
You can’t perform that action at this time.