dopatraman
dopatraman

Reputation: 13908

Passing value to channel is blocking the thread for some reason

I'm using a channel to pass messages from an HTTP handler:

package server

import (
    "bytes"
    "errors"
    "io/ioutil"
    "log"
    "net/http"
)

type Server struct {}

func (s Server) Listen() chan interface{} {
    ch := make(chan interface{})
    http.HandleFunc("/", handle(ch))
    go http.ListenAndServe(":8080", nil)
    return ch
}

func handle(ch chan interface{}) func(http.ResponseWriter, *http.Request) {
    return func(w http.ResponseWriter, r *http.Request) {
        w.Header().Set("Content-Type", "application/json")
        b, err := ioutil.ReadAll(r.Body)
        defer r.Body.Close()
        if err != nil {
            ch <- errors.New(string(500))
            return
        }
        w.Write([]byte("Hello World"))
        log.Print("about to pass to handler channel")
        ch <- bytes.NewBuffer(b)
        log.Print("passed to handler channel")
    }
} 

When I make a request to the server running on port 8080, the thread blocks on this line:

ch <- bytes.NewBuffer(b)

Why is this happening? If you notice, I'm running the listener in a goroutine. I also figured that HTTP handles happen in a separate thread. If I delete the above line, the thread becomes unblocked and the program works as expected. What am I doing wrong?

To clarify, I want to be able to pass the body of a POST request to a channel. Help.

UPDATE: I'm reading from the channel on the main thread:

listenerChan := n.Listen()
go SendRequest("POST", "http://localhost:8080", []byte("hello"))
for listenedMsg := range listenerChan {
    log.Print("listened message>>>> ", listenedMsg)
}

But the thread still blocks on the same line. For clarification, there is nothing wrong with how im sending the request. If I remove the channel send line above, the thread doesnt block.

Upvotes: 0

Views: 412

Answers (3)

Hau Ma
Hau Ma

Reputation: 302

I added missing parts in your code and run it, everything works well. I don't see any block. Here's the code:

package main

import (
    "bytes"
    "errors"
    "io/ioutil"
    "log"
    "net/http"
    "time"
)

type Server struct{}

func (s *Server) Listen() chan interface{} {
    ch := make(chan interface{})
    http.HandleFunc("/", handle(ch))
    go http.ListenAndServe(":8080", nil)
    return ch
}

func handle(ch chan interface{}) func(http.ResponseWriter, *http.Request) {
    return func(w http.ResponseWriter, r *http.Request) {
        w.Header().Set("Content-Type", "application/json")
        b, err := ioutil.ReadAll(r.Body)
        defer r.Body.Close()
        if err != nil {
            ch <- errors.New(string(500))
            return
        }
        w.Write([]byte("Hello World"))
        log.Print("about to pass to handler channel")
        ch <- bytes.NewBuffer(b)
        log.Print("passed to handler channel")
    }
}

// SendRequest send request
func SendRequest(method string, url string, data []byte) {
    tr := &http.Transport{
        MaxIdleConns:       10,
        IdleConnTimeout:    30 * time.Second,
        DisableCompression: true,
    }
    client := &http.Client{Transport: tr}
    reader := bytes.NewReader(data)
    req, err := http.NewRequest(method, url, reader)
    if err != nil {
        panic(err)
    }
    client.Do(req)
}

func main() {
    n := new(Server)
    listenerChan := n.Listen()
    go SendRequest("POST", "http://localhost:8080", []byte("hello"))
    for listenedMsg := range listenerChan {
        log.Print("listened message>>>> ", listenedMsg)
    }
}

And the output are:

2018/06/28 17:22:10 about to pass to handler channel
2018/06/28 17:22:10 passed to handler channel
2018/06/28 17:22:10 listened message>>>> hello

Upvotes: 0

chris
chris

Reputation: 4351

I think @bereal gave a good explanation about using an unbuffered or synchronous channel.

Another way to make things work is to make the channel buffered by changing the line that creates the channel to:

ch := make(chan interface{}, 1)   // added the 1

This will prevent the function from being blocked.

Upvotes: 0

bereal
bereal

Reputation: 34282

Because the channel is unbuffered, the send operation blocks until there's someone who is ready to receive from them. Making the channel buffered will only defer the blocking, so you always need some reading goroutine.

Update to your update: the control flow of the program would go like this:

  1. Server starts listening
  2. main sends the request and waits for the response
  3. Server receives the request and tries to write to the channel
  4. main reads from the channel

4 may happen only after 2, which is blocked by 3 which is blocked because 4 is not happening yet. A classical deadlock.

Upvotes: 2

Related Questions