GithubHelp home page GithubHelp logo

Comments (8)

nmeum avatar nmeum commented on August 23, 2024 1

This should be resolved with 05b8be7; let me know if you have any issues.

Great news, thanks a lot! One thing I noticed though is that the QUIT event doesn't seem to get propagated to a girc.ALL_EVENTS handler, is that intentional? Because I would like to record the quit message in my IRC log through a handler.

The messages does show up in the debug log though.

UPDATE: Never mind, forgot that this is a TX event (which is why it doesn't show up in a ALL_EVENTS handler). Registering a handler for CLIENT_CLOSED works though.

from girc.

lrstanley avatar lrstanley commented on August 23, 2024

You could always send a raw message to handle this. I don't necessarily like the Config route, as users may want to change their quit message before they quit (e.g. 'restarting' vs 'shutdown'). Can probably just implement a wrapper around Client.Close() that can be supplied a string.

from girc.

bmeh avatar bmeh commented on August 23, 2024

I've been playing around with it, and since there is no c.Cmd.Quit(), I did use c.Cmd.SendRawf("QUIT :Received signal '%s' to shut down", sig) right before c.Close(), except the quit message is The TLS connection was non-properly terminated., unless I add a time.Sleep(5 * time.Second) between the two function calls. Any ideas to make c.Cmd.SendRaw{f} blocking perhaps, or what's the correct way to go about this? Adding a wrapper on your end around c.Close() that would receive a string for the QUIT message, or adding c.Cmd.Quit() which would be blocking to avoid incorrect QUIT messages such as the one I mentioned would be nice.

from girc.

lrstanley avatar lrstanley commented on August 23, 2024

If the client sends a QUIT, the server should automatically close the connection when it sees it. Calling Close() right after is probably why you see that message.

Will poke it more when I get a chance.

from girc.

bmeh avatar bmeh commented on August 23, 2024

Actually, scratch the "making c.Cmd.SendRaw{f} blocking part, that would be a horrible idea. If there is such a thing as girc.DISCONNECTED, I'll just add a handler and make(chan struct{}) or something. I'll just have to modify my code to to use for { select {} }, but for that to work (along with auto-reconnect), I'd need girc.DISCONNECTED, which apparently doesn't seem to work as intended. I added a handler for girc.DISCONNECTED, but apparently c.Close() doesn't trigger that event, which I would have suspected, but rather it triggers girc.STOPPED instead. Now, it wouldn't be so bad, except in case of using command !stop (via girc.PRIVMSG handler that matches for !stop), it quits with Connection closed, but calling c.Close() from within my for { select {} } doesn't seem to trigger it, as in, nothing inside that handler gets executed, which I was hoping would happen.

  disconnected := make(chan struct{})
  c.Handlers.Add(girc.STOPPED, func(c *girc.Client, e girc.Event) {
    log.Printf("DISCONNECTED")
    disconnected <- struct{}{}
  })
  c.Handlers.Add(girc.PRIVMSG, func(c *girc.Client, e girc.Event) {
    if strings.HasPrefix(e.Trailing, "!stop") {
      //c.Cmd.SendRaw("QUIT :Received command '!stop'")
      c.Close() // triggers girc.STOPPED
    }
  })
  done := false
  go func() {
    for {
      if err = c.Connect(); err != nil && !done {
        log.Printf("Error connecting to %s (%d): %v",
          cfg.Hostname, cfg.Port, err)
        log.Print("Reconnecting in 15 seconds...")
        time.Sleep(15 * time.Second)
      } else {
        return
      }
    }
  }()

  sigs := make(chan os.Signal, 1)
  signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM, syscall.SIGUSR1)

  for {
    select {
    case sig := <-sigs:
      switch sig {
      case syscall.SIGUSR1:
        continue
      case syscall.SIGINT, syscall.SIGTERM:
        done = true
        log.Printf("Received signal '%s' to shut down", sig)

        if c.IsConnected() {
          // c.Cmd.SendRawf("QUIT :Received signal '%s' to shut down", sig)                                           
          // time.Sleep(5 * time.Second) // ↑ would work with this workaround
          c.Close() // doesn't trigger girc.STOPPED
        }

        return
      }
    case <-disconnected:
      log.Printf("DISCONNECTED #2")
      return
    }
  }

So the issues are:

  1. Neither DISCONNECTED and DISCONNECTED #2 gets printed upon receiving the signals that do call c.Close(). Do you think this is an issue on my end? Due to c.Connect() blocking, I had to call it inside a goroutine, but it works just fine apart from the necessary time.Sleep() between c.Cmd.SendRaw("QUIT :foo") and c.Close(). It is worth noting that they do get printed when I am using the command !stop, which calls c.Close()

  2. It disconnects with the quit message Connection closed upon receiving the matched !stop via girc.PRIVMSG, which cannot be changed without using the time.Sleep() workaround.

  3. I can't quit with a proper quit message when receiving the signals, without using the time.Sleep() workaround.

  4. Coming from another Go IRC library, it uses DISCONNECTED instead of STOPPED, so it was a bit confusing, in fact, I am still not sure what triggers girc.DISCONNECTED. :P

Additionally, omitting c.Close() doesn't seem to work (upon receiving SIGINT/SIGTERM), as c.Cmd.SendRawf() doesn't get executed (since it is non-blocking) before returning, so the quit message remains The TLS connection was non-properly terminated..

If the client sends a QUIT, the server should automatically close the connection when it sees it. Calling Close() right after is probably why you see that message.

The reason I am calling c.Close() is that I assume it does more than just sending QUIT :foo, right?

Hmm, apparently trying this:

        if c.IsConnected() {
          c.Cmd.SendRawf("QUIT :Received signal '%s' to shut down", sig)
          c.Close()
          time.Sleep(2 * time.Second)
        }

        return

does print "DISCONNECTED" and quits with "Connection closed", but it depends on an arbitrary number of seconds, and it's of course non-deterministic, and a terrible workaround.

I did try eliminating c.Close() from this code, but still requires at least time.Sleep(1 * time.Second) to quit with Received signal 'interrupt' to shut down instead of The TLS connection was non-properly terminated.. Note that 1 is an arbitrary number, it may or may not work.


This seems to do the job perfectly with no arbitrary seconds of sleeping with time.Sleep() required, apart from not closing the socket (which is probably not an issue in this case, since we are exiting the program):

for c.IsConnected() {
  c.Cmd.SendRawf("QUIT :Received signal '%s' to shut down", sig)
}

return

Apparently this particular code works in this instance, too:

c.Handlers.Add(girc.PRIVMSG, func(c *girc.Client, e girc.Event) {
  if strings.HasPrefix(e.Trailing, "!stop") {
    for c.IsConnected() {
      c.Cmd.SendRawf("QUIT :Foobar")
    }
  }
})

I get:

<@foo> !stop
-!- bar [bar@nil] has quit [Quit: Foobar]

---

2018/08/31 18:02:49 Error connecting to irc.foo.net (7000): EOF
2018/08/31 18:02:49 Reconnecting in 15 seconds...
2018/08/31 18:03:04 Error connecting to irc.foo.net (7000): Closing link: (unknown@nil) [Quit: Foobar]
2018/08/31 18:03:04 Reconnecting in 15 seconds...

Aaaand it does reconnect and works as expected! I am not sure what to make of the two errors right now, but I guess I could workaround them.

TL;DR: After having spent a few minutes on this, my issue has been resolved and both quitting with a specified error message (in two different instances), and auto-reconnect works.

What's left for you to do is perhaps add c.Cmd.Quit() or the mentioned c.Close() wrapper, although I would recommend documenting that using for c.IsConnected() { c.Cmd.SendRawf("QUIT :Foobar") } works as intended, or perhaps your wrapper could contain this particular code, whichever you prefer. Don't forget to close the socket though!


For completeness' sake:

  done := false
  go func() {
    for {
      if err = c.Connect(); err != nil && !done {
        log.Printf("Error connecting to %s (%d): %v",
          cfg.Hostname, cfg.Port, err)
        log.Print("Reconnecting in 15 seconds...")
        time.Sleep(15 * time.Second)
      } else {
        return
      }
    }
  }()

  sigs := make(chan os.Signal, 1)
  signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM, syscall.SIGUSR1)

  for sig := range sigs {
    switch sig {
    case syscall.SIGUSR1:
      continue
    case syscall.SIGINT, syscall.SIGTERM:
      done = true
      log.Printf("Received signal '%s' to shut down", sig)

      for c.IsConnected() {
        c.Cmd.SendRawf("QUIT :Received signal '%s' to shut down", sig)
      }

      return
    }
  }

from girc.

lrstanley avatar lrstanley commented on August 23, 2024

Utilizing for c.IsConnected() { c.Cmd.SendRawf("QUIT :Foobar") } is prone to data races as well. It could disconnect between c.IsConnected() and the call of c.Cmd.SendRawf(), and it can trigger this bug: #14 (will fix when I get a chance). Additionally, without any delay, you'd be potentially spamming the server with QUIT requests.

The ideal solution is something like Cmd.Quit(reply string), then on the client side, using channels and a timeout, so if it doesn't close the connection after a certain amount of time, the end user then calls Close(). This is where the wrapper may be able to come into play.

from girc.

bmeh avatar bmeh commented on August 23, 2024

You are right about the unnecessary, and potential flooding. Sending QUIT once and then doing for c.IsConnected() { continue } could be sufficient - depending on the use case - otherwise use channels. I decided to go with channels, and named my function closeOrTimeout. :)

from girc.

lrstanley avatar lrstanley commented on August 23, 2024

Sorry for the delay in taking a better look at this. This should be resolved with 05b8be7; let me know if you have any issues.

from girc.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.