') + ')', 'gi'); if (regex.test(text)) { found = true; var frag = document.createDocumentFragment(); var parts = text.split(regex); parts.forEach(function(part, i) { if (i % 2 === 0) { frag.appendChild(document.createTextNode(part)); } else { var span = document.createElement('span'); span.className = 'userscript-highlight'; span.textContent = part; frag.appendChild(span); } }); node.parentNode.replaceChild(frag, node); } }); } else if (node.nodeType === 1 && node.childNodes) { // element var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT']; if (!skipTags.includes(node.tagName)) { Array.from(node.childNodes).forEach(highlight); } } } highlight(document.body); // Re-highlight on dynamic content var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1 || node.nodeType === 3) highlight(node); }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ', 'i'); if (__m === '*' || __re.test(location.href)) { // Strip utm_, fbclid, gclid, etc. from all links on page (function() { var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content', 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid', 'ref', 'ref_src', 'source', 'medium', 'campaign']; function cleanUrl(url) { try { var u = new URL(url, window.location.origin); var changed = false; trackingParams.forEach(function(p) { if (u.searchParams.has(p)) { u.searchParams.delete(p); changed = true; } }); return changed ? u.toString() : url; } catch (e) { return url; } } function cleanLinks() { document.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } cleanLinks(); var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1) { if (node.tagName === 'A') cleanLinks(); node.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + ', 'i'); if (__m === '*' || __re.test(location.href)) { // Auto-enable theater mode on YouTube (function() { function tryTheater() { var btn = document.querySelector('button[aria-label="Theater mode"], ytd-player #player button[title="Theater mode"]'); if (btn && !btn.classList.contains('activated')) { btn.click(); } } // Try immediately tryTheater(); // Try after navigation (SPA) var lastUrl = location.href; setInterval(function() { if (location.href !== lastUrl) { lastUrl = location.href; setTimeout(tryTheater, 500); } }, 1000); // Also try on player load var observer = new MutationObserver(tryTheater); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ', 'i'); if (__m === '*' || __re.test(location.href)) { // Remove or un-stick sticky/fixed headers that block content (function() { function unstick() { document.querySelectorAll('header, nav, [role="banner"], .header, .navbar, .sticky, .fixed-top, [style*="position: fixed"], [style*="position:sticky"]').forEach(function(el) { if (el.style.position === 'fixed' || el.style.position === 'sticky' || getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') { el.style.position = 'static'; el.style.top = 'auto'; el.style.zIndex = 'auto'; } }); } unstick(); var observer = new MutationObserver(unstick); observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] }); })(); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); })(); Hot reload by waralex · Pull Request #25 · plotly/Dash.jl · GitHub
Skip to content

Hot reload - #25

Merged
waralex merged 25 commits into
devfrom
hot_reload
May 22, 2020
Merged

Hot reload#25
waralex merged 25 commits into
devfrom
hot_reload

Conversation

@waralex

@waralexwaralex commented May 19, 2020

Copy link
Copy Markdown
Contributor

Proposed in this PR:

  • Hot reloading support
  • Defining the application path by the path to the executable file
  • The name argument in dash() has been deleted

Dash for Python's "hot-reloading" feature is described in plotly/dash#66, plotly/dash#362, and plotly/dash-renderer#73.

Supporting this as in the Python and R implementations would require

On the Python side, we use the Flask debug reloader to reset the hash. We would need to find a comparable approach using HTTP.jl instead.

@waralex
waralex requested a review from rpkyleMay 19, 2020 14:57
@github-actionsgithub-actionsBot added documentation Improvements or additions to documentation enhancement New feature or request tests labels May 19, 2020
@waralexwaralex linked an issue May 19, 2020 that may be closed by this pull request
@rpkylerpkyle self-assigned this May 19, 2020
Comment threadREADME.md
Comment threadsrc/Dash.jl Outdated
Comment threadsrc/Dash.jl
Comment thread.circleci/config.yml
Comment threadsrc/Dash.jl
Comment threadsrc/handler/state.jl
Comment threadsrc/utils/hot_restart.jl
Comment threadsrc/utils/hot_restart.jl
Comment threadsrc/utils/poll.jl Outdated
Comment threadtest/Project.toml
Co-authored-by: Ryan Patrick Kyle <rpkyle@users.noreply.github.com>
@rpkyle

rpkyle commented May 21, 2020

Copy link
Copy Markdown
Contributor

@waralex I also wanted to mention that we seem to handle interrupts within the REPL a little differently (or Julia does) than those sent while a process is invoked from the CLI:

rpkyle$ julia test.jl [ Info: Running on http://127.0.0.1:8050^C
signal (2): Interrupt:2
in expression starting at /private/tmp/testapp/test.jl:27
fatal: error thrown and no exception handler available.
InterruptException()
kevent at /usr/lib/system/libsystem_kernel.dylib (unknown line)
unknown function (ip:0x0)
Allocations:18621645 (Pool:18617092; Big:4553); GC:18
jl_mutex_unlock at /Users/julia/buildbot/worker/package_macos64/build/src/./locks.h:143 [inlined]
jl_task_get_next at /Users/julia/buildbot/worker/package_macos64/build/src/partr.c:451
signal (15): Terminated:15
in expression starting at /private/tmp/testapp/test.jl:27

I'm not sure why this interrupt isn't caught by the exception handler you've already written, but it would be good to provide our own custom message (like HTTP.jl stopped at http://127.0.0.1:8050) rather than this scary looking error. 🙂

@rpkyle

rpkyle commented May 22, 2020

Copy link
Copy Markdown
Contributor

@waralex I'm not sure if it's related to the revised reloading approach, but I'm seeing an intermittent error related to Servers.jl on page load. We aren't handling the exception right now, so the error dump that appears (IOError: stream is closed or unusable) is pretty gnarly:

rpkyle$ julia test_reload.jl [ Info: Running on http://127.0.0.1:8050
┌ Error: error handling request
│ exception =
│ IOError: stream is closed or unusable
│ Stacktrace:
│ [1] check_open at ./stream.jl:328 [inlined]
│ [2] uv_write_async(::Sockets.TCPSocket, ::Ptr{UInt8}, ::UInt64) at ./stream.jl:961
│ [3] uv_write(::Sockets.TCPSocket, ::Ptr{UInt8}, ::UInt64) at ./stream.jl:924
│ [4] unsafe_write(::Sockets.TCPSocket, ::Ptr{UInt8}, ::UInt64) at ./stream.jl:1007
│ [5] unsafe_write at /Users/rpkyle/.julia/packages/HTTP/GkPBm/src/ConnectionPool.jl:174 [inlined]
│ [6] unsafe_write at ./io.jl:593 [inlined]
│ [7] write at ./io.jl:616 [inlined]
│ [8] startwrite(::HTTP.Streams.Stream{HTTP.Messages.Request,HTTP.ConnectionPool.Transaction{Sockets.TCPSocket}}) at /Users/rpkyle/.julia/packages/HTTP/GkPBm/src/Streams.jl:87
│ [9] handle(::HTTP.Handlers.RequestHandlerFunction{Dash.var"#52#53"{Array{String,1},Int64,HTTP.Handlers.RequestHandlerFunction{Dash.var"#49#50"{Dash.Router,Dash.HandlerState}}}}, ::HTTP.Streams.Stream{HTTP.Messages.Request,HTTP.ConnectionPool.Transaction{Sockets.TCPSocket}}) at /Users/rpkyle/.julia/packages/HTTP/GkPBm/src/Handlers.jl:278
│ [10] #4 at /Users/rpkyle/.julia/packages/HTTP/GkPBm/src/Handlers.jl:345 [inlined]
│ [11] macro expansion at /Users/rpkyle/.julia/packages/HTTP/GkPBm/src/Servers.jl:367 [inlined]
│ [12] (::HTTP.Servers.var"#13#14"{HTTP.Handlers.var"#4#5"{HTTP.Handlers.RequestHandlerFunction{Dash.var"#52#53"{Array{String,1},Int64,HTTP.Handlers.RequestHandlerFunction{Dash.var"#49#50"{Dash.Router,Dash.HandlerState}}}}},HTTP.ConnectionPool.Transaction{Sockets.TCPSocket},HTTP.Streams.Stream{HTTP.Messages.Request,HTTP.ConnectionPool.Transaction{Sockets.TCPSocket}}})() at ./task.jl:358
└ @ HTTP.Servers ~/.julia/packages/HTTP/GkPBm/src/Servers.jl:373

@waralex

waralex commented May 22, 2020

Copy link
Copy Markdown
ContributorAuthor

@rpkyle

[ Info: Running on http://127.0.0.1:8050
┌ Error: error handling request
│ exception =

Apparently, this should be solved in the future PR with logging and exception handling. The error itself most likely occurs when the client close the connection before receiving a response from the server

@waralex

Copy link
Copy Markdown
ContributorAuthor

@rpkyle

I'm not sure why this interrupt isn't caught by the exception handler you've already written, but it would be good to provide our own custom message (like HTTP.jl stopped at http://127.0.0.1:8050) rather than this scary looking error.

I have refused to use ccall(:jl_exit_on_sigint, Cvoid, (Cint,), 0) yet because it is not documented and its behavior is very strange and not very clear to me. So, unfortunately, I will not be able to display custom exit message yet. Julia will just immediately kill the process on the sigint signal.

@rpkyle
rpkyle self-requested a review May 22, 2020 15:33
Comment threadsrc/Dash.jl Outdated
Co-authored-by: Ryan Patrick Kyle <rpkyle@users.noreply.github.com>

@rpkylerpkyle left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

@waralex Great, I think this is OK to merge into dev, pending two minor comments:

  • we currently have an @warn message for hot reloading on L139 of src/Dash.jl, but an error with a similar message on L6 of src/utils/hot_restart.jl. Is this OK? We could exit the hot_restart.jl function after throwing a @warn there, it doesn't have to be an error to interrupt function execution since it's wrapped in if ...end, we can back out within that statement if we wish.
  • this feature needs a CHANGELOG.md entry, as do all new features 🙂 I'll commit the current CHANGELOG.md revision from dev to this branch, so you can do that.

Otherwise, when done, I'd say 💃 . LGTM!

Comment threadsrc/handler/handlers.jl Outdated
Comment threadsrc/handler/handlers.jl Outdated
Comment threadsrc/utils/hot_restart.jl Outdated
end
function hot_restart(func::Function; check_interval = 1., env_key = "IS_HOT_RELOADABLE", suppress_warn = false)
if !is_hot_restart_available()
error("hot restart is disabled for intereactive sessions")

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Suggested change
error("hot restart is disabled for intereactive sessions")
error("hot restart is disabled for interactive sessions")

@waralex

Copy link
Copy Markdown
ContributorAuthor

we currently have an @warn message for hot reloading on L139 of src/Dash.jl, but an error with a similar message on L6 of src/utils/hot_restart.jl. Is this OK? We could exit the hot_restart.jl function after throwing a @warn there, it doesn't have to be an error to interrupt function execution since it's wrapped in if ...end, we can back out within that statement if we wish.

The difference is that in src/Dest.jl we show the warning and execute the if / else branch that doesn't use the hot_restart function
An error in hot_restart is a protection against misuse of this function. If this function is called from REPL, there is no way to continue execution of the program normally, so we throw an exception

@waralex

Copy link
Copy Markdown
ContributorAuthor

this feature needs a CHANGELOG.md entry, as do all new features 🙂 I'll commit the current CHANGELOG.md revision from dev to this branch, so you can do that.

done in 921e3e4

@rpkyle

Copy link
Copy Markdown
Contributor

Labeler failure is likely related to ongoing GitHub issues this afternoon, so will merge as soon as CI test passes; no failures upon disabling Labeler.

@waralex
waralex merged commit b50e5dc into devMay 22, 2020
@rpkyle
rpkyle deleted the hot_reload branch September 24, 2020 02:16
@rpkylerpkyle mentioned this pull request Sep 24, 2020
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentationImprovements or additions to documentationenhancementNew feature or requesttests

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Hot reloading support in Dash.jl

3 participants

@waralex@rpkyle@alexcjohnson