-
-
Notifications
You must be signed in to change notification settings - Fork 1.6k
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
Conversation
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:
This test should pass as long as change notifications fire eventually. |
contrib/src/templates/mod.rs
Outdated
@@ -140,6 +143,37 @@ pub struct TemplateInfo { | |||
data_type: ContentType | |||
} | |||
|
|||
struct ManagedContext( |
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.
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.
contrib/src/templates/mod.rs
Outdated
@@ -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 { |
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'm really not sure how I feel about this Copy
restriction here. It seems very restrictive.
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.
Will revert to the previous Mutex<Option<>> hack
contrib/src/templates/mod.rs
Outdated
@@ -140,6 +143,37 @@ pub struct TemplateInfo { | |||
data_type: ContentType | |||
} | |||
|
|||
struct ManagedContext( |
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 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
.
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 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.
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.
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
contrib/src/templates/mod.rs
Outdated
}; | ||
|
||
#[cfg(debug_assertions)] | ||
let rocket = rocket.attach(AdHoc::on_request(|req, _| { |
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 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()
.
contrib/src/templates/mod.rs
Outdated
|
||
#[cfg(debug_assertions)] | ||
let rocket = rocket.attach(AdHoc::on_request(|req, _| { | ||
let mc = req.guard::<State<ManagedContext>>().succeeded().expect("context wrapper"); |
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.
No panics here! Log an internal error
and proceed normally.
contrib/src/templates/mod.rs
Outdated
@@ -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(|| { |
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.
See my other comment about this being an Option
.
contrib/src/templates/mod.rs
Outdated
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(); |
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.
Just add the .get()
to the thing above.
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 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.
contrib/src/templates/watch.rs
Outdated
use std::sync::Mutex; | ||
use std::time::Duration; | ||
|
||
pub struct TemplateWatcher { |
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.
Do we really need this extra layer of indirection?
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.
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.
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.
These fields and/or behavior are small enough they could maybe move into the debug-only version of mod context
in fairing.rs
.
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.
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.
contrib/src/templates/context.rs
Outdated
/// 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>, |
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.
Why is this an Option
? If there was a failure, we should keep using the old engines.
8850f35
to
4e6fe24
Compare
contrib/src/templates/context.rs
Outdated
pub fn initialize(root: PathBuf) -> Option<Context> { | ||
pub fn initialize( | ||
root: PathBuf, | ||
customize_callback: &(Fn(&mut Engines) + Send + Sync + 'static), |
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.
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
contrib/src/templates/fairing.rs
Outdated
fn info(&self) -> Info { | ||
Info { | ||
name: "Templates", | ||
kind: Kind::Attach | Kind::Request, |
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.
There is probably a speed boost to be gained by only specifying Kind::Request in debug builds, where it is actually used.
contrib/src/templates/watch.rs
Outdated
use std::sync::Mutex; | ||
use std::time::Duration; | ||
|
||
pub struct TemplateWatcher { |
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.
These fields and/or behavior are small enough they could maybe move into the debug-only version of mod context
in fairing.rs
.
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.
This is looking good!
Have you investigated further whether we can use Cargo features in conjunction with cfg
s 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?
contrib/src/templates/fairing.rs
Outdated
} | ||
|
||
fn on_request(&self, req: &mut Request, _data: &Data) { | ||
#[cfg(debug_assertions)] |
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.
Can't we put this on the fn
itself?
contrib/src/templates/context.rs
Outdated
@@ -45,7 +48,8 @@ impl Context { | |||
} | |||
} | |||
|
|||
Engines::init(&templates).map(|engines| { | |||
Engines::init(&templates).map(|mut engines| { | |||
customize_callback(&mut engines); |
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.
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.
contrib/src/templates/fairing.rs
Outdated
impl ContextManager { | ||
pub fn new(ctxt: Context) -> ContextManager { | ||
let root = ctxt.root.clone(); | ||
ContextManager { |
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 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)
}
contrib/src/templates/fairing.rs
Outdated
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) { |
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 an if let
will work better here?
contrib/src/templates/fairing.rs
Outdated
pub use self::context::ContextManager; | ||
|
||
pub struct TemplateFairing { | ||
custom_callback: Box<Fn(&mut Engines) + Send + Sync + 'static>, |
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 create a type alias for the function type instead of using the long type everywhere.
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.
What should this be named? TemplateCustomizationCallback? Or maybe just CustomizationCallback?
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 just CustomEngineFn
? Something ending in Fn
would be good.
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.
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.
contrib/src/templates/fairing.rs
Outdated
}; | ||
|
||
match Context::initialize(template_root.clone(), &*self.custom_callback) { | ||
Some(ctxt) => { Ok(rocket.manage(ContextManager::new(ctxt))) } |
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.
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.
contrib/src/templates/fairing.rs
Outdated
} | ||
|
||
fn on_request(&self, _req: &mut Request, _data: &Data) { | ||
#[cfg(debug_assertions)] |
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.
Use this on the fn
. Not need to create a new block here.
contrib/src/templates/fairing.rs
Outdated
#[cfg(debug_assertions)] | ||
{ | ||
use rocket::State; | ||
let cm = _req.guard::<State<ContextManager>>().unwrap(); |
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.
Rename the argument to req
since it's being used.
contrib/src/templates/mod.rs
Outdated
mod context; | ||
#[cfg(debug_assertions)] mod watch; |
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.
Move this to line 7, after the rest of the cfg
-dependent imports.
contrib/src/templates/watch.rs
Outdated
use std::sync::Mutex; | ||
use std::time::Duration; | ||
|
||
pub struct TemplateWatcher { |
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.
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.
Thanks! Addressed most of those comments. I will check again on the cfg situation -- the problem I encountered before was that As for the |
0687c51
to
e1a43f8
Compare
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 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
contrib/src/templates/fairing.rs
Outdated
/// 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>, |
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.
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.
contrib/src/templates/fairing.rs
Outdated
pub fn new(ctxt: Context) -> ContextManager { | ||
let (tx, rx) = channel(); | ||
|
||
let _watcher = if let Ok(mut watcher) = watcher(tx, Duration::from_secs(1)) { |
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.
Can we use and_then
and map_err
to clean this up?
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.
They can be used (somewhat awkwardly), but they don't appear to clean it up. I'll take another stab at it later though.
contrib/src/templates/fairing.rs
Outdated
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"); |
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.
This line and the other warn!
are too long.
contrib/src/templates/fairing.rs
Outdated
custom_callback(&mut new_ctxt.engines); | ||
*ctxt = new_ctxt; | ||
} else { | ||
warn!("An error occurred while reloading templates. The previous templates will remain active."); |
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.
This line is too long.
contrib/src/templates/fairing.rs
Outdated
|
||
impl TemplateFairing { | ||
pub fn new(custom_callback: Box<Fn(&mut Engines) + Send + Sync + 'static>) -> TemplateFairing | ||
{ |
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.
This should either be at the end of the previous line, or we should split the arguments each into their own line.
contrib/src/templates/fairing.rs
Outdated
} | ||
}; | ||
|
||
match Context::initialize(template_root.clone()) { |
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.
Nice!
contrib/src/templates/fairing.rs
Outdated
#[cfg(debug_assertions)] | ||
fn on_request(&self, req: &mut Request, _data: &Data) { | ||
use rocket::State; | ||
let cm = req.guard::<State<ContextManager>>().unwrap(); |
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.
Do rocket::State
or ::rocket::State
here instead of using rocket::State
at the top.
The trait alias workaround didn't work as easily as I expected, with some errors about missing the 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. |
(adapted from IRC) I tried adding |
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. |
c55c509
to
1af3416
Compare
@jebrosen Looks like the test failed in debug mode. |
Local test output:
Looks like the change is not picked up on the CI. I will switch from using notify's |
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). |
Looks like a race with the test. Perhaps try adding a |
Ah. I've looked over the different methods available on |
@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. |
f5a60c0
to
ddc37e7
Compare
contrib/lib/tests/templates.rs
Outdated
// write a change to the file | ||
write_file(&reload_path, NEW_TEXT); | ||
|
||
let mut attempts = 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'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");
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'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.
contrib/lib/src/templates/fairing.rs
Outdated
} | ||
|
||
if changed { | ||
warn!("Change detected, reloading templates"); |
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.
Should probably be an info!("Change detected: reloading templates.")
.
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.
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.
contrib/lib/src/templates/fairing.rs
Outdated
Some((watcher, Mutex::new(rx))) | ||
} else { | ||
warn!("Could not monitor the templates directory for changes."); | ||
warn!("Live template reload will be unavailable"); |
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.
Should be warn_!
.
contrib/lib/src/templates/fairing.rs
Outdated
} | ||
} else { | ||
warn!("Could not instantiate a filesystem watcher."); | ||
warn!("Live template reload will be unavailable"); |
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.
Should be warn_!
.
contrib/lib/src/templates/fairing.rs
Outdated
pub use self::context::ContextManager; | ||
|
||
pub struct TemplateFairing { | ||
custom_callback: Box<Fn(&mut Engines) + Send + Sync + 'static>, |
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.
Can we add a docstring to say what this is?
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 added one to the type itself. I'm not sure if you meant the fairing or the callback.
contrib/lib/src/templates/fairing.rs
Outdated
self.context.write().unwrap() | ||
} | ||
|
||
pub fn reload_if_needed<F: Fn(&mut Engines)>(&self, custom_callback: F) { |
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.
Can we add a docstring to this method?
contrib/lib/src/templates/mod.rs
Outdated
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)?; |
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.
Presumably we can't call cm.get()
here directly because of some borrow checking issues?
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, 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.
contrib/lib/tests/templates.rs
Outdated
@@ -116,5 +116,57 @@ mod templates_tests { | |||
let response = client.get("/tera/test").dispatch(); | |||
assert_eq!(response.status(), Status::NotFound); | |||
} | |||
|
|||
#[cfg(debug_assertions)] | |||
#[test] |
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.
Can we flip these two attributes so that longer one is at the bottom?
contrib/lib/tests/templates.rs
Outdated
#[cfg(debug_assertions)] | ||
#[test] | ||
fn test_template_reload() { | ||
use rocket::local::Client; |
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.
Do the std
imports before the rocket
imports.
contrib/lib/tests/templates.rs
Outdated
file.sync_all().expect("sync file"); | ||
} | ||
|
||
const RELOAD_TEMPLATE: &'static str = "hbs/reload"; |
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.
const
s should come before everything else. Note that we don't need the 'static
since that's implied.
contrib/lib/tests/templates.rs
Outdated
"tests/templates/hbs/reload.txt.hbs" | ||
); | ||
|
||
write_file(&reload_path, INITIAL_TEXT); |
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.
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
.
5b19181
to
f5a2c88
Compare
contrib/lib/src/templates/fairing.rs
Outdated
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."); |
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.
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.
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.
This is excellent! Aside from these tiny changes (style + 1 docstring), all we need to merge is module level and guide levels. Whew!
contrib/lib/src/templates/fairing.rs
Outdated
self.context.write().unwrap() | ||
} | ||
|
||
/// Checks whether any template files have changed on disk. If |
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.
Word wrap at 80 columns.
contrib/lib/src/templates/fairing.rs
Outdated
|
||
pub use self::context::ContextManager; | ||
|
||
/// The TemplateFairing initializes the template system on attach, |
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.
Word wrap at 80 cols.
contrib/lib/src/templates/fairing.rs
Outdated
/// 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>, |
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.
Can we get a short docstring on custom_callback
as well?
…ilure during reloading
…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
…t can break other concurrent tests
f1c8af1
to
3ecc02a
Compare
Resolves #163.
There are a few general aspects I would like to get feedback on:
notify
in Cargo.toml. it would be nice if this is enabled only when bothdebug_assertions
andfeature="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.template_reload
"). The template reload behavior would now happen incfg(all(debug_assertions, feature="template_reload"))
.The mutex workaround incustom()
. Theon_attach
callback isFn()
, so it must use a workaround such as interior mutability andOption.take()
in order to movef
out of its environment. The interior mutability must also be thread-safe due to theSend + Sync + 'static
bounds, thus the choice ofMutex
. Attach and Launch fairings shouldn't need to beSend + 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.Fairing
type, so this point is no longer relevant.TheContextManager
wrapper. Current consumers ofContext
assume they can access the fields ofContext
(that is, they have aDeref<Target=Context>
, for exampleRwLockReadGuard<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.