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

Disable watch to prevent race condition between threads calling site.process #320

Merged
merged 23 commits into from
Mar 15, 2017

Conversation

parkr
Copy link
Member

@parkr parkr commented Mar 9, 2017

This prevents a race condition with another thread calling site.process.
Before, site.process had to complete before the response would be
correct, which is quite unreliable. Instead, use the appropriate Jekyll
classes to read the item.

Replaces #294.

This prevents a race condition with another thread calling site.process.
Before, site.process had to complete before the response would be
correct, which is quite unreliable. Instead, use the appropriate Jekyll
classes to read the item.
@parkr parkr requested review from mertkahyaoglu and benbalter March 9, 2017 19:42
Copy link
Member

@mertkahyaoglu mertkahyaoglu left a comment

Choose a reason for hiding this comment

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

Wow 😄 We were really overthinking. Great solution 👍

Copy link
Contributor

@benbalter benbalter 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 might need to do this for static files and data files as well.

@@ -20,7 +20,9 @@ class Server < Sinatra::Base
end

write_file(write_path, page_body)
updated_page = pages.find { |p| p.path == write_path }
updated_page = Jekyll::Page.new(
site, site.source, File.dirname(write_path), File.basename(write_path)
Copy link
Contributor

Choose a reason for hiding this comment

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

Is the path sanitized prior to being read? (to prevent directory traversal from the user-supplied write_path?

Copy link
Member Author

@parkr parkr Mar 9, 2017

Choose a reason for hiding this comment

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

@benbalter The write_path comes from either document_path or request_path. In my opinion this should most certainly be sanitized before it gets here as it's used to interact with the filesystem before this line.

updated_document = collection.docs.find { |d| d.path == write_path }
updated_document = Jekyll::Document.new(write_path, {
:collection => collection, :site => site,
}).tap(&:read)
render_404 if updated_document.nil?
Copy link
Member

Choose a reason for hiding this comment

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

If we rename updated_document to document, we can use ensure_document which does the same thing. Same for ensure_page.

Copy link
Member Author

Choose a reason for hiding this comment

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

We won't be able to do this, because ensure_document calls document, which uses the buggy way of reading:

 80       def document
 81         collection.docs.find { |d| d.path == document_path }
 82       end

Copy link
Member

Choose a reason for hiding this comment

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

Isn't document overwritten by Jekyll::Document.new ? So ensure_document uses newly created document object instead of the result object of document method.

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 guessing methods' priority is higher over objects in ruby so it will use document method anyway?

@parkr
Copy link
Member Author

parkr commented Mar 9, 2017

I think we might need to do this for static files and data files as well.

Static files, yes, but data files use their own API: DataFile.new(params["data_file_id"]) which reads in the file manually.

collection.docs.find { |d| d.path == document_path } ||
Jekyll::Document.new(write_path, {
:collection => collection, :site => site,
}).tap(&:read)
Copy link
Member

Choose a reason for hiding this comment

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

This is really 🆒 You da boss 😎

@@ -85,7 +84,9 @@ def relative_page_path
end

def page
site.pages.find { |p| sanitized_path(p.path) == page_path }
Jekyll::Page.new(
Copy link
Member

@mertkahyaoglu mertkahyaoglu Mar 9, 2017

Choose a reason for hiding this comment

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

Missed || here or is this on purpose?

Copy link
Member Author

Choose a reason for hiding this comment

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

You could do either.

Copy link
Contributor

Choose a reason for hiding this comment

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

I think he's asking if we want to look to the existing page first then fall back to creating a new one like we do for other content types?

@@ -29,8 +29,7 @@ class Server < Sinatra::Base
end

write_file(write_path, document_body)
updated_document = collection.docs.find { |d| d.path == write_path }
render_404 if updated_document.nil?
ensure_document
json updated_document.to_api(:include_content => true)
Copy link
Member

Choose a reason for hiding this comment

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

This needs to be document now instead of updated_document.

@@ -20,8 +20,7 @@ class Server < Sinatra::Base
end

write_file(write_path, page_body)
updated_page = pages.find { |p| p.path == write_path }
render_404 if updated_page.nil?
ensure_page
json updated_page.to_api(:include_content => true)
Copy link
Member

@mertkahyaoglu mertkahyaoglu Mar 10, 2017

Choose a reason for hiding this comment

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

This needs to be page.

@mertkahyaoglu
Copy link
Member

mertkahyaoglu commented Mar 11, 2017

This should be good to go after deleting test files (unless failing tests generating them).

@parkr
Copy link
Member Author

parkr commented Mar 12, 2017

This should be good to go after deleting test files (unless failing tests generating them).

@mertkahyaoglu Would you mind taking this PR and running with it? I am not able to commit any more time to it unfortunately but I think we have the right approach here. 😄

@mertkahyaoglu
Copy link
Member

@parkr Got it 👍

I reverted 1203e14 because write_path becomes undefined in page, document methods.

Another thing is that static files tests are failing when JekyllAdmin::StaticFile.new is used.

Here are the failing tests;

rspec ./spec/jekyll-admin/server/static_file_spec.rb:30 # static_files 404s when a static file doesn't exist
rspec ./spec/jekyll-admin/server/static_file_spec.rb:35 # static_files returns a deep directory listing
rspec ./spec/jekyll-admin/server/static_file_spec.rb:51 # static_files returns a directory listing starting only at the root

@benbalter Any solution for this? Or shall we revert this approach for static files.

@benbalter
Copy link
Contributor

@mertkahyaoglu: @parkr is out of the office this week, but I'll take a look this morning and see what I can do here to get the build passing.

return found if found
return unless File.file?(write_path)
Jekyll::Page.new(
site, site.source, File.dirname(write_path), File.basename(write_path)
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 want the full path for dir ?

@ashmaroli
Copy link
Member

For the test, can we write to a file manually then expect the regeneration to not happen? I don't know how we can check whether the regeneration occurs doing that.

sent a PR #324 inspired by this comment.

@benbalter
Copy link
Contributor

Full props go to @ashmaroli. If tests are green, I believe this can be merged.

@mertkahyaoglu
Copy link
Member

mertkahyaoglu commented Mar 15, 2017

@ashmaroli @benbalter @parkr Awesome work guys. Many thanks. This and #323 deserve a release.

@mertkahyaoglu mertkahyaoglu merged commit 6dd908b into master Mar 15, 2017
@mertkahyaoglu mertkahyaoglu deleted the prevent-race-condition-on-change branch March 15, 2017 21:19
@ashmaroli
Copy link
Member

Thank you for the acknowledgement @benbalter and @mertkahyaoglu 😃 🍻

@ashmaroli
Copy link
Member

This and #323 deserve a release.

I feel #321 should be addressed by the next release. Either with the merge of #322 (or another better implementation), or an entry to the documentation informing users about the situation.

Another TODO:

I believe the only thing remaining here would be to document the limitation.

from Ben's comment above.

@mertkahyaoglu mertkahyaoglu changed the title When updating a document or page, read item by itself Disable watch to prevent race condition between threads calling site.process Mar 16, 2017
@benbalter benbalter mentioned this pull request Mar 20, 2017
mertkahyaoglu added a commit that referenced this pull request Mar 22, 2017
mertkahyaoglu added a commit that referenced this pull request Mar 22, 2017
@mertkahyaoglu mertkahyaoglu mentioned this pull request Mar 26, 2017
@mariusa
Copy link

mariusa commented Jul 22, 2017

This introduces other issues, see #340

Not sure why the admin has to take over re-generation when enabled as a plugin? Just write files to disk, like all editors. Then jekyll would do it's job and re-generate content on changes, no matter if files are changed via admin or other editors.

Thanks

@ashmaroli
Copy link
Member

From Jekyll Admin's online docs

Note: Since there are two Sinatra servers that might call site.process concurrently, Jekyll Admin disables --watch flag to prevent a race condition between these servers that might cause incorrect responses for the API.
This ensures that the site is regenerated by only the process that Jekyll Admin runs.

@mariusa
Copy link

mariusa commented Jul 22, 2017

The current editing of .css, layouts or includes is a pain. We have to stop jekyll and start again on each css edit, to see changes.
What solutions do you recommend?

The simplest one does see to limit jekyll-admin to edit files as any IDE, just write them to local disk. Don't try to generate anything, jekyll already handles that well, and it works for all assets.

@ashmaroli
Copy link
Member

The simplest solution when editing "non-content files" is to comment out jekyll-admin from your _config.yml or Gemfile depending on how its being used..

@mariusa
Copy link

mariusa commented Jul 22, 2017

So

  1. edit some pages in jekyll-admin
  2. realize we need to edit some CSS/JS/include
  3. stop jekyll
  4. comment out jekyll-admin
  5. start jekyll
  6. multiple CSS edits
  7. stop jekyll
  8. uncomment jekyll-admin
  9. start jekyll
  10. back to page editing

Would you agree it's cumbersome?

@ashmaroli
Copy link
Member

Would you agree it's cumbersome?

Definitely.
I'm planning to release a gem-version of my fork of jekyll-admin soon.. It'll support editing css and other non-content files via the interface.. (still disables --watch though..)

@mertkahyaoglu
Copy link
Member

Actually I'm planning to bring the watch back. With the changes I described here, the possibility of hitting a race condition is very low (%0 for me, please test #415 locally and let me know if you encounter a race condition. It will work in dev mode for you to test). If the possibility is acceptable, I think we should give it a shot.

@ashmaroli
Copy link
Member

I'm planning to release a gem-version of my fork

FYI, released under gem-name jekyll-manager

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants