• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

happy-sdk / happy / 15980077914

30 Jun 2025 05:53PM UTC coverage: 46.685% (-5.3%) from 51.943%
15980077914

push

github

mkungla
wip: gohappy cmd

Signed-off-by: Marko Kungla <marko.kungla@gmail.com>

0 of 281 new or added lines in 9 files covered. (0.0%)

2059 existing lines in 30 files now uncovered.

7943 of 17014 relevant lines covered (46.69%)

97527.29 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

45.16
/sdk/instance/instance.go
1
// SPDX-License-Identifier: Apache-2.0
2
//
3
// Copyright © 2022 The Happy Authors
4

5
package instance
6

7
import (
8
        "bytes"
9
        "crypto/sha1"
10
        "encoding/hex"
11
        "errors"
12
        "fmt"
13
        "log/slog"
14
        "os"
15
        "path/filepath"
16
        "strconv"
17
        "syscall"
18
        "time"
19

20
        "github.com/happy-sdk/happy/sdk/internal"
21
        "github.com/happy-sdk/happy/sdk/session"
22
)
23

24
type Instance struct {
25
        id      ID
26
        sess    *session.Context
27
        pidfile string
28
}
29

30
var Error = errors.New("instance error")
31

32
type ID string
33

34
func NewID() ID {
12✔
35
        hasher := sha1.New()
12✔
36
        _, _ = fmt.Fprint(hasher, time.Now().UnixMilli())
12✔
37
        hashSum := hasher.Sum(nil)
12✔
38
        fullID := hex.EncodeToString(hashSum)
12✔
39
        return ID(fullID[:8])
12✔
40
}
12✔
41

42
func (id ID) String() string {
30✔
43
        return string(id)
30✔
44
}
30✔
45

46
// New creates a new instance for the application.
47
func New(sess *session.Context) (*Instance, error) {
9✔
48
        if sess == nil {
9✔
UNCOV
49
                return nil, fmt.Errorf("%w: session is nil", Error)
×
UNCOV
50
        }
×
51

52
        pidsdir := sess.Opts().Get("app.fs.path.pids").String()
9✔
53
        if _, err := os.Stat(pidsdir); err != nil {
9✔
UNCOV
54
                return nil, fmt.Errorf("%w: pids directory not found: %s", Error, pidsdir)
×
UNCOV
55
        }
×
56

57
        pidfiles, err := verifyPidFiles(sess, pidsdir)
9✔
58
        if err != nil {
9✔
UNCOV
59
                return nil, fmt.Errorf("%w: failed to read pids directory: %s", Error, err.Error())
×
UNCOV
60
        }
×
61

62
        inst := &Instance{
9✔
63
                id:   ID(sess.Opts().Get("app.instance.id").Value().String()),
9✔
64
                sess: sess,
9✔
65
        }
9✔
66

9✔
67
        if len(pidfiles) >= sess.Settings().Get("app.instance.max").Value().Int() {
9✔
UNCOV
68
                sess.Log().Error("existing pid files")
×
69
                for _, pidfile := range pidfiles {
×
70
                        sess.Log().Println(filepath.Join(pidsdir, pidfile.Name()))
×
UNCOV
71
                }
×
UNCOV
72
                return nil, fmt.Errorf("%w: max instances reached (%s)", Error, sess.Settings().Get("app.instance.max").String())
×
73
        }
74

75
        inst.pidfile = filepath.Join(
9✔
76
                pidsdir,
9✔
77
                fmt.Sprintf("instance-%s.pid", inst.id.String()),
9✔
78
        )
9✔
79
        internal.Log(sess.Log(), "create pid lock file", slog.String("file", inst.pidfile))
9✔
80

9✔
81
        if err := os.WriteFile(inst.pidfile, []byte(inst.sess.Opts().Get("app.pid").Value().String()), 0644); err != nil {
9✔
UNCOV
82
                return nil, fmt.Errorf("%w: failed to write intance PID file: %s", Error, err.Error())
×
83
        }
×
84

85
        return inst, nil
9✔
86
}
87

88
func (inst *Instance) Dispose() error {
9✔
89
        internal.Log(inst.sess.Log(), "disposing instance", slog.String("id", inst.id.String()))
9✔
90
        // delete the pidfile
9✔
91
        if _, err := os.Stat(inst.pidfile); err == nil {
9✔
UNCOV
92
                if err := os.Remove(inst.pidfile); err != nil {
×
UNCOV
93
                        return fmt.Errorf("failed to delete pidfile %s: %w", inst.pidfile, err)
×
UNCOV
94
                }
×
UNCOV
95
                if inst.sess != nil {
×
UNCOV
96
                        internal.Log(inst.sess.Log(), "successfully deleted pidfile", slog.String("file", inst.pidfile))
×
97
                }
×
98
        }
99
        return nil
9✔
100
}
101

102
func verifyPidFiles(sess *session.Context, pidsdir string) ([]os.DirEntry, error) {
9✔
103

9✔
104
        pidfiles, err := os.ReadDir(pidsdir)
9✔
105
        if err != nil {
9✔
UNCOV
106
                return nil, err
×
107
        }
×
108

109
        var res []os.DirEntry
9✔
110
        for _, pidfile := range pidfiles {
9✔
111
                if pidfile.IsDir() {
×
112
                        continue
×
113
                }
UNCOV
114
                if ok, err := verifyPidFile(sess, pidfile); err != nil {
×
UNCOV
115
                        return nil, err
×
UNCOV
116
                } else if ok {
×
UNCOV
117
                        res = append(res, pidfile)
×
UNCOV
118
                }
×
119
        }
120
        return res, nil
9✔
121
}
122

UNCOV
123
func verifyPidFile(sess *session.Context, pidfile os.DirEntry) (ok bool, err error) {
×
UNCOV
124
        pidfileAbs := filepath.Join(sess.Opts().Get("app.fs.path.pids").Value().String(), pidfile.Name())
×
UNCOV
125
        data, err := os.ReadFile(pidfileAbs)
×
126
        if err != nil {
×
127
                return false, err
×
UNCOV
128
        }
×
129
        pid := string(bytes.TrimSpace(data))
×
130
        if pid == "" {
×
131
                return false, nil
×
132
        }
×
133
        pidInt, err := strconv.Atoi(pid)
×
UNCOV
134
        if err != nil {
×
UNCOV
135
                return false, err
×
UNCOV
136
        }
×
137

138
        p, err := os.FindProcess(pidInt)
×
139
        if err != nil {
×
140
                return false, err
×
141
        }
×
142
        if err := p.Signal(syscall.Signal(0)); err != nil {
×
143
                sess.Log().Notice(fmt.Sprintf("previous process %d: %s", pidInt, err.Error()))
×
144
                return false, os.Remove(pidfileAbs)
×
145
        }
×
146
        return true, nil
×
147
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc