library: io: handle retryable errors

Discord client being absent/disconnecting is not fatal to the RPC sender, in cases like that we return ErrAgain and in the case of broken pipe (Discord client going away) also reset the Client state so the caller can choose to retry the operation and therefore initiate a new connection attempt.

Signed-off-by: Ophestra Umiker <cat@ophivana.moe>
This commit is contained in:
Ophestra Umiker 2024-06-20 01:14:04 +09:00
parent a985e2b9df
commit b3325f56b1
Signed by: cat
SSH Key Fingerprint: SHA256:gQ67O0enBZ7UdZypgtspB2FDM1g3GVw8nX0XSdcFw8Q
2 changed files with 22 additions and 0 deletions

13
io.go
View File

@ -3,9 +3,11 @@ package rpcfetch
import (
"encoding/binary"
"encoding/json"
"errors"
"net"
"os"
"strconv"
"syscall"
)
type Client struct {
@ -33,6 +35,17 @@ func Raw[T any](d *Client, opcode uint32, payload any) (uint32, T, error) {
// Raw writes a raw payload to Discord and returns the response opcode and payload
func (d *Client) Raw(opcode uint32, payload any) (uint32, []byte, error) {
opcodeResp, payloadResp, err := d.raw(opcode, payload)
if errors.Is(err, syscall.EPIPE) {
// clean up as much as possible
_ = d.Close()
// advise retry
err = ErrAgain
}
return opcodeResp, payloadResp, err
}
func (d *Client) raw(opcode uint32, payload any) (uint32, []byte, error) {
if err := binary.Write(d.conn, binary.LittleEndian, opcode); err != nil {
return 0, nil, err
}

View File

@ -1,16 +1,25 @@
package rpcfetch
import (
"errors"
"io/fs"
"net"
"time"
)
var (
ErrAgain = errors.New("operation not performed")
)
func (d *Client) dial() error {
if d.dialed {
panic("attempted to dial on open client")
}
if conn, err := net.DialTimeout("unix", sockPath()+"/discord-ipc-0", 5*time.Second); err != nil {
if errors.Is(err, fs.ErrNotExist) {
return ErrAgain
}
return err
} else {
d.conn = conn