Example #1
0
// 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()
}
Example #2
0
// 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()
}