Long Polling, "Global" Button, Broadcast to everyone?
Asked Answered
A

2

3

I am trying to implement a global button counter that updates as any/different users click it. So the idea is if one person clicks the button, I see the counter update on my instance of the page.

I currently have the long polling technique working, or so I think, but after review I believe I have an error with "broadcasting" the update to all browsers.

The error currently is that if for example I have two browsers open, and I continuously click on one browser, that browser that I click the button only updates half the time. It will get 1 3 5 etc while the other browser displays 2 4 6 etc.

After reviewing online, I think this may have to do with channels and broadcasting to all those browsers that are on the site. If anyone can help me with an example of how I might send the update to all browsers, every time, I'd really appreciate it.

Client:

<html>
<script language=javascript>

function longpoll(url, callback) {

    var req = new XMLHttpRequest (); 
    req.open ('GET', url, true); 

    req.onreadystatechange = function (aEvt) {
        if (req.readyState == 4) { 
            if (req.status == 200) {
                callback(req.responseText);
                longpoll(url, callback);
            } else {
                alert ("long-poll connection lost");
            }
        }
    };

    req.send(null);
}

function recv(msg) {

    var box = document.getElementById("counter");

    box.innerHTML += msg + "\n";
}
function send() {


    var box = document.getElementById("counter");

  var req = new XMLHttpRequest (); 
    req.open ('POST', "/push?rcpt=", true); 

    req.onreadystatechange = function (aEvt) {
        if (req.readyState == 4) { 
            if (req.status == 200) {
            } else {
                alert ("failed to send!");
            }
        }
  };
  req.send("hi")

  //box.innerHTML += "test" ;  
}
</script>
<body onload="longpoll('/poll', recv);">

<h1> Long-Poll Chat Demo </h1>

<p id="counter"></p>
<button onclick="send()" id="test">Test Button</button>
</body>
</html>

Server:

package main

import (
    "net/http"
    "log"
    "io"
//  "io/ioutil"
  "strconv"
)

var messages chan string = make(chan string, 100)

var counter = 0

func PushHandler(w http.ResponseWriter, req *http.Request) {

    //body, err := ioutil.ReadAll(req.Body)

    /*if err != nil {
        w.WriteHeader(400)
    }*/
    counter += 1
    messages <- strconv.Itoa(counter)
}


func PollResponse(w http.ResponseWriter, req *http.Request) {

    io.WriteString(w, <-messages)
}

func main() {
    http.Handle("/", http.FileServer(http.Dir("./")))
    http.HandleFunc("/poll", PollResponse)
    http.HandleFunc("/push", PushHandler)
    err := http.ListenAndServe(":8010", nil)
    if err != nil {
        log.Fatal("ListenAndServe: ", err)
    }
}
Arreola answered 6/11, 2013 at 0:51 Comment(8)
can you add some debug output and see when the push requests are going through?Rudolf
@Rudolf if you give me more instruction on what you'd like, I'd be happy to oblige. The push requests go through with the send() function on the client side, which is activated whenever a user pushes the buttonArreola
I would add some fmt.Println() calls to confirm WHEN those calls are going through as opposed to WHEN the poll responses are going through. I'll run a local test in an hour or so and see what results I can get (I'm at work currently and can't test your code)Rudolf
@Rudolf As far as I can tell they're going through as expected, one after the other. But thank you so much for helping me out! I really appreciate it! I look forward to hearing back from you in a bit!Arreola
I got it running locally and it works as expected. The number are sequential. I'm testing on Linux/Chromium, what are you testing with? Can you manually request poll and push URLs without your Javascript? Does it work differently?Rudolf
@Brendan I have it running in Chrome. Make sure you open two separate tabs running the client. I just checked again, no tweaks since I've posted, and I still have it not updating in both tabs. They alternateArreola
let us continue this discussion in chatRudolf
Out of the topic, maybe you wnat to try HTML5 websocket with godoc.org/code.google.com/p/go.net/websocketRigmarole
R
8

The issue isn't the Go code(alone;see PS PS), it's the browser(Chrome). Making 2 requests to the same URL happens in sequence, not in parallel.

Solution You need to add a unique timestamp to the longpoll URL to trick the browser:

req.open ('GET', url+"?"+(new Date().getTime()), true); 

PS - I learned a lot about Go channels and mutex with this question. Thanks :)

PS PS - James' answer (https://mcmap.net/q/182789/-long-polling-quot-global-quot-button-broadcast-to-everyone) is key to getting the server side Go code to handle more than 1 request at a time, because Go channels are blocking which means only 1 goroutine can receive at a time. So the solution to the OP's question is a combination of front and back end code changes.

Rudolf answered 6/11, 2013 at 7:0 Comment(4)
Cannot explain how much I appreciate you working with me in chat, for several hours, on getting this answer! Everyone up vote!Arreola
The Go code in the question was also broken thoug, right? This alone can't solve the problem.Insecurity
@JamesHenstridge I wondered that, but I had already switched to Mutex code you suggested. I'd like to switch back to the initial code to verify this, but I believe your answer was key because channels cannot be read by more than 1 goroutine.Rudolf
@JamesHenstridge I've updated my answer to give you credit for the Go code changes. Thank you.Rudolf
I
9

Go channels are not multi-cast. That is, if you have multiple goroutines reading from the channel, only one will wake up when you write a value to the channel rather than it being broadcast to all readers.

One alternative would be to use a condition variable instead:

var (
    lock sync.Mutex
    cond = sync.NewCond(&lock)
)

In your PollResponse handler, you can wait on the condition with:

lock.Lock()
cond.Wait()
message := strconv.Itoa(counter)
lock.Unlock()
// write message to response

In your PushHandler handler, you can broadcast the change with:

lock.Lock()
counter += 1
cond.Broadcast()
lock.Unlock()
Insecurity answered 6/11, 2013 at 2:37 Comment(11)
terrible question, but I also get frustrated with Go's documentation... which package do those come from. I've tried importing from sync, cond, lock, and then sync/cond, and sync/lock as well. I'm not getting it right thoughArreola
sync is the package name, so import "sync" will do.Insecurity
In the PollReponse handler, would I write the message to the resopnse in the same way utlizing io.WriteString(w, <-messages)Arreola
if I add that line after your comments, but actually io.WriteString(w, message), where message is the variable you utilize in your solution, the problem still persists the sameArreola
Really? I just tried modifying your example code with the changes I suggested, and testing it with curl shows that multiple /poll requests get woken from a single /push request.Insecurity
I have tried... I encourage you to copy paste the code and see what happens with two windows open on the client. Its the same :(Arreola
Here's the version I tried: paste.ubuntu.com/6368785 -- I ran curl http://localhost:8010/poll in two terminal windows, and ran curl http://localhost:8010/push in a third. The first two windows blocked until I executed the command in the third, at which point they both printed the same counter value.Insecurity
Is there something wrong with my long polling code on the client side then? That's the only thing I can think of but I don't see any glaring issuesArreola
@JamesHenstridge I confirm it works in curl, the problem was chrome only allows 1 request at a time!! I proved this by trying localhost and 127.0.0.1 at the same time and I got the correct results (same results as curl)Rudolf
Perhaps you're running into the browser's limit on simultaneous connections to a single site or a caching issue thenInsecurity
I googled the issue and it seems specific to Chrome. I don't believe there is a user configurable setting, it's just an internal mechanism.Rudolf
R
8

The issue isn't the Go code(alone;see PS PS), it's the browser(Chrome). Making 2 requests to the same URL happens in sequence, not in parallel.

Solution You need to add a unique timestamp to the longpoll URL to trick the browser:

req.open ('GET', url+"?"+(new Date().getTime()), true); 

PS - I learned a lot about Go channels and mutex with this question. Thanks :)

PS PS - James' answer (https://mcmap.net/q/182789/-long-polling-quot-global-quot-button-broadcast-to-everyone) is key to getting the server side Go code to handle more than 1 request at a time, because Go channels are blocking which means only 1 goroutine can receive at a time. So the solution to the OP's question is a combination of front and back end code changes.

Rudolf answered 6/11, 2013 at 7:0 Comment(4)
Cannot explain how much I appreciate you working with me in chat, for several hours, on getting this answer! Everyone up vote!Arreola
The Go code in the question was also broken thoug, right? This alone can't solve the problem.Insecurity
@JamesHenstridge I wondered that, but I had already switched to Mutex code you suggested. I'd like to switch back to the initial code to verify this, but I believe your answer was key because channels cannot be read by more than 1 goroutine.Rudolf
@JamesHenstridge I've updated my answer to give you credit for the Go code changes. Thank you.Rudolf

© 2022 - 2024 — McMap. All rights reserved.