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

router/mux: Added an unhandler and helpers #157

Closed
wants to merge 2 commits into from

Conversation

cognusion
Copy link

I needed to be able to remove routes on the fly (in conjunction with service discovery), so I added router.unhandler() to safely remove handlers from the router, and helper functions too. If there are any changes you'd prefer to see, please let me know.

router.unhandler is used to safely remove handlers from the router.
"Un" helper functions in mux exposed to "undo" each of the method types
as well. Very simple test added on to existing test.
@elithrar
Copy link
Contributor

elithrar commented Sep 4, 2015

Would it be possible to have a map of routes and call Compile() after
updating the map? https://godoc.org/github.com/zenazn/goji/web#Mux.Compile
On Sat, 5 Sep 2015 at 6:08 am M@ notifications@github.com wrote:

I needed to be able to remote routes on the fly (in conjunction with
service discovery), so I added router.unhandler() to safely remove handlers
from the router, and helper functions too. If there are any changes you'd

prefer to see, please let me know.

You can view, comment on, or merge this pull request online at:

#157
Commit Summary

  • router/mux: Added an unhandler and helpers

File Changes

Patch Links:


Reply to this email directly or view it on GitHub
#157.

@cognusion
Copy link
Author

The last line of unhandler is rt.setMachine(nil) which calls the compiler.

@elithrar
Copy link
Contributor

elithrar commented Sep 7, 2015

Yep - noticed that. To be clearer: what does this look like with your PR
vs. the current public API? This adds 10 methods to the mux for what is a
very specific use-case. If you can achieve the same thing in your
application (since Compile is public) it's going to keep the API surface
for others a lot smaller.

Note: @zenazn calls the shots on this - but I think we might agree here.

On Sat, Sep 5, 2015 at 7:42 AM M@ notifications@github.com wrote:

The last line of unhandler is rt.setMachine(nil) which calls the compiler.


Reply to this email directly or view it on GitHub
#157 (comment).

@cognusion
Copy link
Author

I added the helper methods to keep the same feel as the original code, as opposed to just one method where the bitmask is passed in. I can certainly nix the Un* functions if the author prefers.

@zenazn
Copy link
Owner

zenazn commented Sep 8, 2015

I don't want to encourage mutating live muxes—the synchronization required to do so safely is kind of annoying (originally Goji allowed mutating the middleware stack concurrently with live requests, but I've since backed those changes out because of how complicated it was. I've been meaning to do the same for handlers as well, even though that's a much more vanilla RCU).

As an alternative, have you tried to implement a custom Pattern? I'm not sure exactly what your use-case is here, but if you have a bunch of homogenous-ish dynamic URLs, it might make more sense to implement that as a Pattern instead of adding and removing routes dynamically.

If that doesn't fit well with your use case, I'd love to hear more about what you're after—maybe there's another solution that's less intrusive.

@cognusion
Copy link
Author

I originally wrote my own mux based on gorilla/mux, however I appreciated how goji elegantly passed the context around, and was very fast, so I rebased my project using it instead. I could do all of the work outside of the mux, sure, but having a dynamic multiplexer is kind of the point. Feel free to close out the PR if removing routes isn't of interest.

@zenazn
Copy link
Owner

zenazn commented Sep 8, 2015

Alright, fair enough.

Would a layer of indirection be helpful here? I'm imagining something like this:

goji.Get("/static", Route)
m := web.New()
goji.Handle("/*", func(c web.C, w http.ResponseWriter, r *http.Request) {
    m.ServeHTTPC(c, w, r)
})
func reload() {
    newm := web.New()
    // add routes
    m = newm
}

(with some additional locking or sync/atomic, of course). This would let you populate a new mux dynamically and swap it in to production. Since you throw out the old mux every time, there's no need to drop routes (or even to remember which routes ought to be dropped, which might be easier depending on your data representation)

@cognusion
Copy link
Author

I'm not sure how you'd do that without a stop-the-world pause and probably dumping all in-flight requests. By only messing with the Route array, I'm at most losing requests that were being served to now-dead endpoints (routes) which is fine (and handled), and the pauses for route changes are ~3-10 microsecond blinks, even on pretty sizable (subjective, I suppose) route arrays

@zenazn
Copy link
Owner

zenazn commented Sep 8, 2015

Here's a complete working example. All requests are completed using the Mux retrieved from the atomic.Value, and in-flight requests will finish using the old Mux as expected. And if you pre-Compile your new Mux before you swap it in you should see pauses on the order of tens of clock cycles.

package main

import (
    "io"
    "net/http"
    "sync/atomic"

    "github.com/zenazn/goji"
    "github.com/zenazn/goji/web"
)

func main() {
    a := web.New()
    a.Handle("/*", func(w http.ResponseWriter, r *http.Request) {
        io.WriteString(w, "Mux A")
    })
    b := web.New()
    b.Handle("/*", func(w http.ResponseWriter, r *http.Request) {
        io.WriteString(w, "Mux B")
    })

    var mux atomic.Value
    mux.Store(a)

    goji.Get("/a", func(w http.ResponseWriter, r *http.Request) {
        io.WriteString(w, "Switching to A")
        mux.Store(a)
    })
    goji.Get("/b", func(w http.ResponseWriter, r *http.Request) {
        io.WriteString(w, "Switching to B")
        mux.Store(b)
    })
    goji.Handle("/*", func(c web.C, w http.ResponseWriter, r *http.Request) {
        mux.Load().(*web.Mux).ServeHTTPC(c, w, r)
    })

    goji.Serve()
}

@cognusion
Copy link
Author

Yeah, using atomic saves some complexity I was imagining. Looks good to me, thank you!

@cognusion
Copy link
Author

While not quite as fast, it is acceptably fast at scale to mux-swap @zenazn @elithrar Thank you for your insights. Closing PR.

@cognusion cognusion closed this Sep 9, 2015
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants