// Run the engine. Engine.Run() will exit when the engine is closed or // disconnected. You can check for errors after exit with Engine.Error(). // func (eng *Engine) Run() error { C.pn_connection_engine_start(&eng.engine) // Channels for read and write buffers going in and out of the read/write goroutines. // The channels are unbuffered: we want to exchange buffers in seuquence. readsIn, writesIn := make(chan []byte), make(chan []byte) readsOut, writesOut := make(chan []byte), make(chan []byte) wait := sync.WaitGroup{} wait.Add(2) // Read and write goroutines go func() { // Read goroutine defer wait.Done() for { rbuf, ok := <-readsIn if !ok { return } n, err := eng.conn.Read(rbuf) if n > 0 { readsOut <- rbuf[:n] } else if err != nil { _ = eng.Inject(func() { eng.Transport().Condition().SetError(err) C.pn_connection_engine_read_close(&eng.engine) }) return } } }() go func() { // Write goroutine defer wait.Done() for { wbuf, ok := <-writesIn if !ok { return } n, err := eng.conn.Write(wbuf) if n > 0 { writesOut <- wbuf[:n] } else if err != nil { _ = eng.Inject(func() { eng.Transport().Condition().SetError(err) C.pn_connection_engine_write_close(&eng.engine) }) return } } }() for eng.dispatch() { readBuf := eng.readBuffer() writeBuf := eng.writeBuffer() // Note that getting the buffers can generate events (eg. SASL events) that // might close the transport. Check if we are already finished before // blocking for IO. if !eng.dispatch() { break } // sendReads/sendWrites are nil (not sendable in select) unless we have a // buffer to read/write var sendReads, sendWrites chan []byte if readBuf != nil { sendReads = readsIn } if writeBuf != nil { sendWrites = writesIn } // Send buffers to the read/write goroutines if we have them. // Get buffers from the read/write goroutines and process them // Check for injected functions select { case sendReads <- readBuf: case sendWrites <- writeBuf: case buf := <-readsOut: C.pn_connection_engine_read_done(&eng.engine, C.size_t(len(buf))) case buf := <-writesOut: C.pn_connection_engine_write_done(&eng.engine, C.size_t(len(buf))) case f, ok := <-eng.inject: // Function injected from another goroutine if ok { f() } case <-eng.timer.C: eng.tick() } } eng.err.Set(EndpointError(eng.Connection())) eng.err.Set(eng.Transport().Condition().Error()) close(readsIn) close(writesIn) close(eng.running) // Signal goroutines have exited and Error is set, disable Inject() _ = eng.conn.Close() // Close conn, force read/write goroutines to exit (they will Inject) wait.Wait() // Wait for goroutines for _, h := range eng.handlers { switch h := h.(type) { case cHandler: C.pn_handler_free(h.pn) } } C.pn_connection_engine_final(&eng.engine) return eng.err.Get() }
// Run the engine. Engine.Run() will exit when the engine is closed or // disconnected. You can check for errors after exit with Engine.Error(). // func (eng *Engine) Run() error { // Channels for read and write buffers going in and out of the read/write goroutines. // The channels are unbuffered: we want to exchange buffers in seuquence. readsIn, writesIn := make(chan []byte), make(chan []byte) readsOut, writesOut := make(chan []byte), make(chan []byte) wait := sync.WaitGroup{} wait.Add(2) // Read and write goroutines go func() { // Read goroutine defer wait.Done() for { rbuf, ok := <-readsIn if !ok { return } n, err := eng.conn.Read(rbuf) if n > 0 { readsOut <- rbuf[:n] } else if err != nil { eng.inject <- func() { eng.Transport().Condition().SetError(err) C.pn_connection_engine_read_close(&eng.engine) } return } } }() go func() { // Write goroutine defer wait.Done() for { wbuf, ok := <-writesIn if !ok { return } n, err := eng.conn.Write(wbuf) if n > 0 { writesOut <- wbuf[:n] } else if err != nil { eng.inject <- func() { eng.Transport().Condition().SetError(err) C.pn_connection_engine_write_close(&eng.engine) } return } } }() for !C.pn_connection_engine_finished(&eng.engine) { // Enable readIn/writeIn channles only if we have a buffer. readBuf, writeBuf := eng.buffers() var sendReads, sendWrites chan []byte if readBuf != nil { sendReads = readsIn } if writeBuf != nil { sendWrites = writesIn } // Send buffers to the read/write goroutines if we have them. // Get buffers from the read/write goroutines and process them select { case sendReads <- readBuf: case sendWrites <- writeBuf: case buf := <-readsOut: if len(buf) > 0 { C.pn_connection_engine_read_done(&eng.engine, C.size_t(len(buf))) } else { panic(fmt.Sprintf("read buf %v", buf)) } case buf := <-writesOut: if len(buf) > 0 { C.pn_connection_engine_write_done(&eng.engine, C.size_t(len(buf))) } else { panic(fmt.Sprintf("write buf %v", buf)) } case f, ok := <-eng.inject: // Function injected from another goroutine if ok { f() } } for { cevent := C.pn_connection_engine_dispatch(&eng.engine) if cevent == nil { break } event := makeEvent(cevent, eng) for _, h := range eng.handlers { h.HandleEvent(event) } } } eng.err.Set(EndpointError(eng.Connection())) eng.err.Set(eng.Transport().Condition().Error()) close(readsIn) close(writesIn) eng.conn.Close() // Make sure connection is closed wait.Wait() // Wait for goroutines close(eng.running) // Signal goroutines have exited and Error is set. C.pn_connection_engine_final(&eng.engine) for _, h := range eng.handlers { switch h := h.(type) { case cHandler: C.pn_handler_free(h.pn) } } // FIXME aconway 2016-06-21: consistent error handling return eng.err.Get() }