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

Reload templates when they have been changed on disk #537

Closed
wants to merge 28 commits into from

Conversation

jebrosen
Copy link
Collaborator

@jebrosen jebrosen commented Jan 13, 2018

Resolves #163.

There are a few general aspects I would like to get feedback on:

  • The dependency on notify in Cargo.toml. it would be nice if this is enabled only when both debug_assertions and feature="templates" are active, i.e. target.'cfg(all(debug_assertions, feature="templates"))'.dependencies. In its current form, notify is a dependency for all debug builds but remains unused. I will continue to search for a solution to this, but it seems cargo does not support all the cfg() options that rust does, at least not in some places.
    • Side note: what about making this a separate feature, instead of being based on build type? That would make this bullet point obsolete, and allow consumers to control template reloading independently of the build type.
    • EDIT (2018-04-15): Discussions on IRC have led to keeping notify as a dependency in all debug builds, and also adding a new default feature (perhaps "template_reload"). The template reload behavior would now happen in cfg(all(debug_assertions, feature="template_reload")).
  • The mutex workaround in custom(). The on_attach callback is Fn(), so it must use a workaround such as interior mutability and Option.take() in order to move f out of its environment. The interior mutability must also be thread-safe due to the Send + Sync + 'static bounds, thus the choice of Mutex. Attach and Launch fairings shouldn't need to be Send + Sync #522 would make this workaround unnecessary, but that issue is nowhere near trivial to fix and might necessitate some amount of change to the entire Fairings system.
    • EDIT (2018-04-12): Changed approach by using a new Fairing type, so this point is no longer relevant.
  • The ContextManager wrapper. Current consumers of Context assume they can access the fields of Context (that is, they have a Deref<Target=Context>, for example RwLockReadGuard<Context>).
  • This is a difficult feature to write automated tests for, and likely unreliable. Not all filesystems on all platforms are guaranteed to have reliable filesystem notification, and failing a CI build because of something nondeterministic like change notification timing would be unfortunate.
    • EDIT (2018-07-10): There is a test now, and it strives to be reliable on CI. Hopefully it actually is.
  • User-facing and internal documentation.

@jebrosen jebrosen changed the title Template reload Reload templates when they have been changed on disk Jan 13, 2018
@Mononofu
Copy link

Looking forward to this!

In the meantime, pseudo code for one possible way of testing in the face of unreliable change notification, if you don't want to use a mock:

initial = fetch_url()

timeout = now() + 5s
modify_template()
while fetch_url() == initial && now() < timeout:
  modify_template()
  sleep(10ms)

expect_ne(initial, fetch_url())

This test should pass as long as change notifications fire eventually.

@@ -140,6 +143,37 @@ pub struct TemplateInfo {
data_type: ContentType
}

struct ManagedContext(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add a docstring explaining what's going on with this structure: what it's used for, why it exists, and why it looks this way.

@@ -203,7 +237,7 @@ impl Template {
/// # ;
/// }
/// ```
pub fn custom<F>(f: F) -> impl Fairing where F: Fn(&mut Engines) + Send + Sync + 'static {
pub fn custom<F>(f: F) -> impl Fairing where F: Fn(&mut Engines) + Copy + Send + Sync + 'static {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm really not sure how I feel about this Copy restriction here. It seems very restrictive.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will revert to the previous Mutex<Option<>> hack

@@ -140,6 +143,37 @@ pub struct TemplateInfo {
data_type: ContentType
}

struct ManagedContext(
Copy link
Member

@SergioBenitez SergioBenitez Apr 9, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think I'd rather see:

#[cfg(not(debug_assertions))]
mod context {
    struct ManagedContext(Context);

    impl ManagedContext {
        fn new(ctxt: Context) -> ManagedContext {
            ManagedContext(ctxt)
        }

        fn get(&self) -> impl Deref<Target=Context> {
            &self.0
        }
    }
}

#[cfg(debug_assertions)]
mod context {
    use std::sync::{RwLock, RwLockReadGuard, RwLockWriteGuard};

    struct ManagedContext(RwLock<Context>);

    impl ManagedContext {
        fn new(ctxt: Context) -> ManagedContext {
            ManagedContext(RwLock::new(ctxt))
        }

        fn get(&self) -> impl Deref<Target=Context> {
            self.0.read().unwrap()
        }

        fn get_mut(&self) -> impl DerefMut<Target=Context> {
            self.0.write().unwrap()
        }
    }
}

Note the use of impl Deref.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't like that get_mut is only included in the debug_assertions case. I suppose it's fine, but it would be nice if these were symmetric.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

get_mut can only even exist because of the RwLock in debug_assertions; the structure is asymmetric because the release build does not need to write to Context and can keep the read-only form

};

#[cfg(debug_assertions)]
let rocket = rocket.attach(AdHoc::on_request(|req, _| {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we're better off creating a custom Fairing implementation at this point as opposed to using AdHoc. This is really confusing. It took me a while to realize that this is what's actually going to reload the templates.

I think we want to create a new file, fairing.rs or something, that contains ManagedContext and some new type, TemplateFairing, for which we implement Fairing for. This time it will have both on_attach and on_request handlers. The custom method should then get really really small, probably just: TemplateFairing::new().


#[cfg(debug_assertions)]
let rocket = rocket.attach(AdHoc::on_request(|req, _| {
let mc = req.guard::<State<ManagedContext>>().succeeded().expect("context wrapper");
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No panics here! Log an internal error and proceed normally.

@@ -315,7 +355,12 @@ impl Template {
Status::InternalServerError
})?;

let string = ctxt.engines.render(name, &info, value).ok_or_else(|| {
let engines = ctxt.engines.as_ref().ok_or_else(|| {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See my other comment about this being an Option.

error_!("Uninitialized template context: missing fairing.");
info_!("To use templates, you must attach `Template::fairing()`.");
info_!("See the `Template` documentation for more information.");
Status::InternalServerError
})?;
let ctxt = mc.get();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just add the .get() to the thing above.

Copy link
Collaborator Author

@jebrosen jebrosen Apr 9, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I will see what happens with other refactors and check this again, but I believe this was done because ctxt borrows from mc, so the expression assigned to mc can't be a temporary.

use std::sync::Mutex;
use std::time::Duration;

pub struct TemplateWatcher {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we really need this extra layer of indirection?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

By extra layer do you mean the watch.rs file, or the entire structure? The motivation for this was to keep the notify dependency relatively isolated in the code.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These fields and/or behavior are small enough they could maybe move into the debug-only version of mod context in fairing.rs.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, that's what I'm thinking as well. Play around with it a bit. If we don't need the indirection, and it doesn't make code significantly cleaner, let's get rid of it.

/// Mapping from template name to its information.
pub engines: Engines
/// Loaded template engines, or None if there was a failure during a reload
pub engines: Option<Engines>,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is this an Option? If there was a failure, we should keep using the old engines.

pub fn initialize(root: PathBuf) -> Option<Context> {
pub fn initialize(
root: PathBuf,
customize_callback: &(Fn(&mut Engines) + Send + Sync + 'static),
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would be cleaner to remove the Send+Sync+'static bounds here, it's only at the level of the Fairing that these bounds are necessary

fn info(&self) -> Info {
Info {
name: "Templates",
kind: Kind::Attach | Kind::Request,
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is probably a speed boost to be gained by only specifying Kind::Request in debug builds, where it is actually used.

use std::sync::Mutex;
use std::time::Duration;

pub struct TemplateWatcher {
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These fields and/or behavior are small enough they could maybe move into the debug-only version of mod context in fairing.rs.

Copy link
Member

@SergioBenitez SergioBenitez left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is looking good!

Have you investigated further whether we can use Cargo features in conjunction with cfgs to allow users to disable template reloading if they want but enable by default (and only) on debug builds? IE, so that we include template reloading only when the program is compiled in debug mode with the template_reloading feature (or some other name) enabled?

}

fn on_request(&self, req: &mut Request, _data: &Data) {
#[cfg(debug_assertions)]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can't we put this on the fn itself?

@@ -45,7 +48,8 @@ impl Context {
}
}

Engines::init(&templates).map(|engines| {
Engines::init(&templates).map(|mut engines| {
customize_callback(&mut engines);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should call the user's function after initialization. That way, we don't have to pass it in to initialize at all, and Context can stay ignorant that it even exists.

impl ContextManager {
pub fn new(ctxt: Context) -> ContextManager {
let root = ctxt.root.clone();
ContextManager {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think you can reorder this so that you don't need the let binding:

ContextManager {
    watcher: TemplateWather::new(ctxt.root.clone()),
    context: RwLock::new(ctxt)
}

if self.watcher.as_ref().map(TemplateWatcher::needs_reload).unwrap_or(false) {
warn!("Change detected, reloading templates");
let mut ctxt = self.get_mut();
match Context::initialize(ctxt.root.clone(), custom_callback) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe an if let will work better here?

pub use self::context::ContextManager;

pub struct TemplateFairing {
custom_callback: Box<Fn(&mut Engines) + Send + Sync + 'static>,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You should create a type alias for the function type instead of using the long type everywhere.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What should this be named? TemplateCustomizationCallback? Or maybe just CustomizationCallback?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe just CustomEngineFn? Something ending in Fn would be good.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is all that changes, is it still worth it?

diff --git a/contrib/src/templates/fairing.rs b/contrib/src/templates/fairing.rs
index ab15dd3..74c6e12 100644
--- a/contrib/src/templates/fairing.rs
+++ b/contrib/src/templates/fairing.rs
@@ -104,12 +104,14 @@ mod context {
 
 pub use self::context::ContextManager;
 
+pub type CustomEngineFn = Fn(&mut Engines) + Send + Sync + 'static;
+
 pub struct TemplateFairing {
-    custom_callback: Box<Fn(&mut Engines) + Send + Sync + 'static>,
+    custom_callback: Box<CustomEngineFn>,
 }
 
 impl TemplateFairing {
-    pub fn new(custom_callback: Box<Fn(&mut Engines) + Send + Sync + 'static>) -> TemplateFairing
+    pub fn new(custom_callback: Box<CustomEngineFn>) -> TemplateFairing
     {
         TemplateFairing { custom_callback }
     }

The only other place all the bounds show up is in a trait bound, where the type alias can't be used.

};

match Context::initialize(template_root.clone(), &*self.custom_callback) {
Some(ctxt) => { Ok(rocket.manage(ContextManager::new(ctxt))) }
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As in the previous comment, we don't need to pass the function to the Context initialization at all. Just call it after the context returns. Better code, and nicer separation of concerns to boot.

}

fn on_request(&self, _req: &mut Request, _data: &Data) {
#[cfg(debug_assertions)]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use this on the fn. Not need to create a new block here.

#[cfg(debug_assertions)]
{
use rocket::State;
let cm = _req.guard::<State<ContextManager>>().unwrap();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rename the argument to req since it's being used.

mod context;
#[cfg(debug_assertions)] mod watch;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Move this to line 7, after the rest of the cfg-dependent imports.

use std::sync::Mutex;
use std::time::Duration;

pub struct TemplateWatcher {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, that's what I'm thinking as well. Play around with it a bit. If we don't need the indirection, and it doesn't make code significantly cleaner, let's get rid of it.

@jebrosen
Copy link
Collaborator Author

Thanks! Addressed most of those comments.

I will check again on the cfg situation -- the problem I encountered before was that Cargo.toml didn't seem to support the same cfg flags that rustc does.

As for the TemplateWatcher, I will try moving that into ContextManager and see how it changes things.

@jebrosen jebrosen force-pushed the template_reload branch 2 times, most recently from 0687c51 to e1a43f8 Compare April 18, 2018 15:30
Copy link
Member

@SergioBenitez SergioBenitez left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we're really close now!

About the type alias: it's unfortunate that what we really need is a trait alias. An RFC for trait aliases has actually been accepted (rust-lang/rfcs#1733), but according to the tracking issue, it is unimplemented (rust-lang/rust#41517).

We can use a hack for now:

trait AliasName: TraitsToAlias { }
impl<T: TraitsToAlias> AliasName for T { }

Then you can use AliasName anywhere you would have used TraitsToAlias

/// The current template context, inside an RwLock so it can be updated
context: RwLock<Context>,
/// A filesystem watcher. Unused in the code after creation, but must be kept alive
_watcher: Option<RecommendedWatcher>,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copying from IRC:

[09:01:45]  ~Sergio:	occultus: What's the deal with _watcher?
[09:01:54]  ~Sergio:	It seems really odd that we create a thing and never use it, but it has to be kept around.
[09:04:10]  ~Sergio:	occultus: Without reading any additional documentation, my guess is that it has to be kept around for the `recv_queue` to have something to read from. If that's the case, then we should group the `RecommendedWatcher` and the `rx` inside the `Option`. No use in calling `recv()` on an `rx` that won't ever have anything.
[09:05:13]  ~Sergio:	IE, we should change `_watcher: Option<RecommendedWatcher>` to `watcher: Option<(RecommendedWatcher, Mutex<Receiver<DebouncedEvent>>)>`, or something to that affect.

pub fn new(ctxt: Context) -> ContextManager {
let (tx, rx) = channel();

let _watcher = if let Ok(mut watcher) = watcher(tx, Duration::from_secs(1)) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we use and_then and map_err to clean this up?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

They can be used (somewhat awkwardly), but they don't appear to clean it up. I'll take another stab at it later though.

if watcher.watch(ctxt.root.clone(), RecursiveMode::Recursive).is_ok() {
Some(watcher)
} else {
warn!("Could not monitor the templates directory for changes. Live template reload will be unavailable");
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This line and the other warn! are too long.

custom_callback(&mut new_ctxt.engines);
*ctxt = new_ctxt;
} else {
warn!("An error occurred while reloading templates. The previous templates will remain active.");
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This line is too long.


impl TemplateFairing {
pub fn new(custom_callback: Box<Fn(&mut Engines) + Send + Sync + 'static>) -> TemplateFairing
{
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should either be at the end of the previous line, or we should split the arguments each into their own line.

}
};

match Context::initialize(template_root.clone()) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice!

#[cfg(debug_assertions)]
fn on_request(&self, req: &mut Request, _data: &Data) {
use rocket::State;
let cm = req.guard::<State<ContextManager>>().unwrap();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do rocket::State or ::rocket::State here instead of using rocket::State at the top.

@jebrosen
Copy link
Collaborator Author

jebrosen commented Apr 19, 2018

The trait alias workaround didn't work as easily as I expected, with some errors about missing the Output associated type of FnOnce, which I attempted to solve a few ways demonstrated here:

https://play.rust-lang.org/?gist=cd5cdbf4680729afb0c8a8feb724bd37&version=nightly

EDIT: I found rust-lang/rust#24010 , which is 3 (!) years old and appears to be what I ran into.

@jebrosen
Copy link
Collaborator Author

(adapted from IRC) I tried adding template_reload as a feature that is on by default, but I realized most of the docs use default_features = false on rocket_contrib and I suspect that isn't an uncommon situation. Alternatively, an opt-in feature called no_template_reload would permit the desired behavior. Thoughts?

@jebrosen
Copy link
Collaborator Author

jebrosen commented Apr 29, 2018

Sorry about going off the radar for a while. In summary, I think this is what's still unaddressed as far as design:

I would like feedback/a decision on the first two points, and I was hoping to write some tests within the next few days.

@SergioBenitez
Copy link
Member

@jebrosen Looks like the test failed in debug mode.

@jebrosen
Copy link
Collaborator Author

jebrosen commented Jul 2, 2018

Local test output:

GET /:
Warning: Change detected, reloading templates
    => Error: No matching routes for GET /.
    => Warning: Responding with 404 Not Found catcher.

Looks like the change is not picked up on the CI. I will switch from using notify's DebouncedEvent to RawEvent in case it is a timing issue.

@jebrosen
Copy link
Collaborator Author

jebrosen commented Jul 4, 2018

So far, that's one CI build that failed with raw_watcher, and one CI build that succeeded with raw_watcher. I haven't had the test fail locally (yet).

@SergioBenitez
Copy link
Member

Looks like a race with the test. Perhaps try adding a flush() or sink() before checking that the new template was picked up. Additionally, or instead, you may want to add a short, say, 100ms delay between writing and checking for a reload.

@jebrosen
Copy link
Collaborator Author

jebrosen commented Jul 8, 2018

Ah. I've looked over the different methods available on File and their implementations and I will try sync_all for this. I had assumed that Drop would do the right thing, but I forgot that an immediate sync isn't guaranteed. In the meantime, do you have any thoughts on my earlier comments about the trait alias and the feature declaration?

@SergioBenitez
Copy link
Member

@jebrosen I don't think the repetition is a big deal. I think the best we can do is:

type CallBack = Box<Fn(&mut Engines) + Send + Sync + 'static>;

But even that may not be worth it. I wouldn't worry about it too much. I think if we can get the tests to work reliably (i.e, 100% of the time, otherwise it will make life much harder down the road), then we can proceed to final polish.

@jebrosen jebrosen force-pushed the template_reload branch 2 times, most recently from f5a60c0 to ddc37e7 Compare July 12, 2018 00:56
// write a change to the file
write_file(&reload_path, NEW_TEXT);

let mut attempts = 0;
Copy link
Member

@SergioBenitez SergioBenitez Jul 14, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd prefer if it was written the following way (I did 6 just because 1.5s):

for i in 0..6 {
    // dispatch any request to trigger a template reload
    client.get("/").dispatch();

    // if the new content is correct, we are done
    let new_rendered = Template::show(client.rocket(), RELOAD_TEMPLATE, ());
    if new_rendered == Some(NEW_TEXT.into()) {
        remove_file(reload_path).expect("delete template file");
        return;
    }

    thread::sleep(Duration::from_millis(250));
}

panic!("failed to reload template in 1.5s");

Copy link
Member

@SergioBenitez SergioBenitez left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm pretty happy with this. After these mostly minor changes, we'll need some docs in the main template module as well as in the guide explaining how this works. In particular, those docs should make it clear when templates are reloaded (debug mode only, when a file write occurs, on the next request) and the performance implications.

}

if changed {
warn!("Change detected, reloading templates");
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should probably be an info!("Change detected: reloading templates.").

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this maybe be info_! as well? I have to admit I'm not sure when to use the indented forms and when not to.

Some((watcher, Mutex::new(rx)))
} else {
warn!("Could not monitor the templates directory for changes.");
warn!("Live template reload will be unavailable");
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should be warn_!.

}
} else {
warn!("Could not instantiate a filesystem watcher.");
warn!("Live template reload will be unavailable");
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should be warn_!.

pub use self::context::ContextManager;

pub struct TemplateFairing {
custom_callback: Box<Fn(&mut Engines) + Send + Sync + 'static>,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we add a docstring to say what this is?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I added one to the type itself. I'm not sure if you meant the fairing or the callback.

self.context.write().unwrap()
}

pub fn reload_if_needed<F: Fn(&mut Engines)>(&self, custom_callback: F) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we add a docstring to this method?

error_!("Uninitialized template context: missing fairing.");
info_!("To use templates, you must attach `Template::fairing()`.");
info_!("See the `Template` documentation for more information.");
Status::InternalServerError
})?;
let ctxt = cm.get();

let (render, content_type) = self.finalize(&ctxt)?;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Presumably we can't call cm.get() here directly because of some borrow checking issues?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, and I think I may have finally figured out why. The State type implements Deref, whose lifetime is tied to the State object itself. Using .inner() returns a longer lifetime and "escapes" out of the temporary State value.

@@ -116,5 +116,57 @@ mod templates_tests {
let response = client.get("/tera/test").dispatch();
assert_eq!(response.status(), Status::NotFound);
}

#[cfg(debug_assertions)]
#[test]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we flip these two attributes so that longer one is at the bottom?

#[cfg(debug_assertions)]
#[test]
fn test_template_reload() {
use rocket::local::Client;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do the std imports before the rocket imports.

file.sync_all().expect("sync file");
}

const RELOAD_TEMPLATE: &'static str = "hbs/reload";
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

consts should come before everything else. Note that we don't need the 'static since that's implied.

"tests/templates/hbs/reload.txt.hbs"
);

write_file(&reload_path, INITIAL_TEXT);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add a comment above this. I suppose it's // set up the template with the initial text _before_ initializing the Rocket instance so that it's picked up on the initial load.

if watcher.watch(ctxt.root.clone(), RecursiveMode::Recursive).is_ok() {
Some((watcher, Mutex::new(rx)))
} else {
warn_!("Could not monitor the templates directory for changes.");
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, so, the _ just adds a level of indentation to allow for grouping messages. Here, we'd want the one on the top not to use the _ so we get this:

Warning: Could not monitor the templates directory for changes.
    => Live template reload will be unavailable.

In other words, the _ is for cosmetic/grouping purposes.

Copy link
Member

@SergioBenitez SergioBenitez left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is excellent! Aside from these tiny changes (style + 1 docstring), all we need to merge is module level and guide levels. Whew!

self.context.write().unwrap()
}

/// Checks whether any template files have changed on disk. If
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Word wrap at 80 columns.


pub use self::context::ContextManager;

/// The TemplateFairing initializes the template system on attach,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Word wrap at 80 cols.

/// In debug mode, the fairing checks for modifications to templates
/// before every request and reloads them if necessary.
pub struct TemplateFairing {
pub(crate) custom_callback: Box<Fn(&mut Engines) + Send + Sync + 'static>,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we get a short docstring on custom_callback as well?

…emplateWatcher into the debug version of ManagedContext
hopefully helps catch reload conditions more quickly, and notify
won't do any of its extra processing (that we ignore anyway)
… systems for changes to be detected properly
@SergioBenitez
Copy link
Member

Merged in 491b04c. Whew! So happy to finally land this. Excellent job as always, @jebrosen!

@jebrosen jebrosen deleted the template_reload branch August 11, 2018 14:43
@SergioBenitez SergioBenitez added the pr: merged This pull request was merged manually. label Aug 17, 2018
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
pr: merged This pull request was merged manually.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Option to reload or prevent caching of templates
3 participants