Skip to content

Commit

Permalink
simplifiy locking moving session views and device state under their o…
Browse files Browse the repository at this point in the history
…wn lock
  • Loading branch information
scottfeldman committed Jan 13, 2025
1 parent 3783578 commit 4b18056
Show file tree
Hide file tree
Showing 24 changed files with 191 additions and 233 deletions.
191 changes: 66 additions & 125 deletions pkg/device/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -166,101 +166,64 @@ func (d *device) _renderSession(w io.Writer, template, sessionId string,
return d._renderTmpl(w, template, data)
}

func (d *device) renderSession(w io.Writer, template, sessionId string, level int) error {
d.RLock()
defer d.RUnlock()
return d._renderSession(w, template, sessionId, level, map[string]any{})
}

func (d *device) _renderChildren(w io.Writer, s *session, level int) error {

if len(d.Children) == 0 {
return nil
}

// Collect child devices from d.Children
var children []*device
for _, childId := range d.Children {
if child, exists := devices[childId]; exists {
children = append(children, child)
}
}

// TODO allow other sort methods?

// Sort the collected child devices by ToLower(device.Name)
sort.Slice(children, func(i, j int) bool {
return strings.ToLower(children[i].Name) < strings.ToLower(children[j].Name)
})

// Render the child devices in sorted order
for _, child := range children {

view, _, err := s._lastView(child.Id)
if err != nil {
// If there was no view saved, default to overview
view = "overview"
}

if err := child.render(w, s, "/device", view, level, map[string]any{}); err != nil {
return err
}
}

return nil
}

func (d *device) _render(w io.Writer, s *session, path, view string,
func (d *device) _render(w io.Writer, sessionId, path, view string,
level int, data map[string]any) error {

path = strings.TrimPrefix(path, "/")
template := path + "-" + view + ".tmpl"

//LogDebug("_render", "id", d.Id, "session-id", s.Id,
//LogDebug("_render", "id", d.Id, "session-id", sessionId,
// "path", path, "level", level, "template", template)
if err := d._renderSession(w, template, s.sessionId, level, data); err != nil {
if err := d._renderSession(w, template, sessionId, level, data); err != nil {
return err
}

s._save(d.Id, view, level)
d.saveView(sessionId, view, level)

return nil
}

func (d *device) render(w io.Writer, s *session, path, view string,
func (d *device) _renderWalk(w io.Writer, sessionId, path, view string,
level int, data map[string]any) error {

// We're going to walk children, so hold devices lock
devicesMu.RLock()
defer devicesMu.RUnlock()

return d._render(w, sessionId, path, view, level, data)
}

func (d *device) render(w io.Writer, sessionId, path, view string,
level int, data map[string]any) error {

d.RLock()
defer d.RUnlock()
return d._render(w, s, path, view, level, data)

return d._renderWalk(w, sessionId, path, view, level, data)
}

func (d *device) _renderPkt(w io.Writer, s *session, pkt *Packet) error {
func (d *device) renderPkt(w io.Writer, sessionId string, pkt *Packet) error {
var data map[string]any

view, level, err := s._lastView(d.Id)
if err != nil {
return err
}

var data = make(map[string]any)
view, level := d.lastView(sessionId)
json.Unmarshal(pkt.Msg, &data)

//LogDebug("_renderPkt", "id", d.Id, "view", view, "level", level, "pkt", pkt)
return d._render(w, s, pkt.Path, view, level, data)
}
if data == nil {
data = make(map[string]any)
}

func (d *device) renderPkt(w io.Writer, s *session, pkt *Packet) error {
d.RLock()
defer d.RUnlock()
return d._renderPkt(w, s, pkt)

//LogDebug("renderPkt", "id", d.Id, "view", view, "level", level, "pkt", pkt)
return d._render(w, sessionId, pkt.Path, view, level, data)
}

func (d *device) renderTemplate(name string, data any) (template.HTML, error) {
func (d *device) _renderTemplate(name string, data any) (template.HTML, error) {
var buf bytes.Buffer

if err := d.renderTmpl(&buf, name, data); err != nil {
if err := d._renderTmpl(&buf, name, data); err != nil {
return template.HTML(""), err
}

return template.HTML(buf.String()), nil
}

Expand All @@ -273,56 +236,57 @@ func RenderTemplate(w io.Writer, id, name string, data any) error {
return fmt.Errorf("RenderTemplate unknown device id %s", id)
}

func (d *device) renderView(sessionId, path, view string, level int) (template.HTML, error) {
func (d *device) _renderView(sessionId, path, view string, level int) (template.HTML, error) {
var buf bytes.Buffer

sessionsMu.RLock()
defer sessionsMu.RUnlock()

s, ok := sessions[sessionId]
if !ok {
return template.HTML(""), fmt.Errorf("Unknown session %s", sessionId)
}

s.Lock()
defer s.Unlock()

// We're going to walk children, so hold devices lock
devicesMu.RLock()
defer devicesMu.RUnlock()

if err := d.render(&buf, s, path, view, level, map[string]any{}); err != nil {
if err := d._renderWalk(&buf, sessionId, path, view, level,
map[string]any{}); err != nil {
return template.HTML(""), err
}

s._save(d.Id, view, level)

return template.HTML(buf.String()), nil
}

func (d *device) renderChildren(sessionId string, level int) (template.HTML, error) {
var buf bytes.Buffer
func (d *device) _renderChildrenWrite(w io.Writer, sessionId string, level int) error {

sessionsMu.RLock()
defer sessionsMu.RUnlock()
if len(d.Children) == 0 {
return nil
}

s, ok := sessions[sessionId]
if !ok {
return template.HTML(""), fmt.Errorf("Unknown session %s", sessionId)
// Collect child devices from d.Children
var children []*device
for _, childId := range d.Children {
if child, exists := devices[childId]; exists {
children = append(children, child)
}
}

s.Lock()
defer s.Unlock()
// TODO allow other sort methods?

// We're going to walk children, so hold devices lock
devicesMu.RLock()
defer devicesMu.RUnlock()
// Sort the collected child devices by ToLower(device.Name)
sort.Slice(children, func(i, j int) bool {
return strings.ToLower(children[i].Name) < strings.ToLower(children[j].Name)
})

if err := d._renderChildren(&buf, s, level); err != nil {
return template.HTML(""), err
// Render the child devices in sorted order
for _, child := range children {
view, _ := child.lastView(sessionId)
child.RLock()
if err := child._render(w, sessionId, "/device", view, level,
map[string]any{}); err != nil {
child.RUnlock()
return err
}
child.RUnlock()
}

return template.HTML(buf.String()), nil
return nil
}

func (d *device) _renderChildren(sessionId string, level int) (template.HTML, error) {
var buf bytes.Buffer
err := d._renderChildrenWrite(&buf, sessionId, level)
return template.HTML(buf.String()), err
}

func (d *device) serveStaticFile(w http.ResponseWriter, r *http.Request) {
Expand Down Expand Up @@ -416,34 +380,11 @@ func (d *device) showModelDocs(w http.ResponseWriter, r *http.Request) {

func (d *device) showView(w http.ResponseWriter, r *http.Request) {
view := r.URL.Query().Get("view")

sessionId := r.Header.Get("session-id")

sessionsMu.RLock()
defer sessionsMu.RUnlock()

s, ok := sessions[sessionId]
if !ok || !s.connected() {
// Session expired, force full page refresh to start new session
w.Header().Set("HX-Refresh", "true")
return
}

s.Lock()
defer s.Unlock()

_, level, err := s._lastView(d.Id)
if err != nil {
_, level := d.lastView(sessionId)
if err := d.render(w, sessionId, "/device", view, level, map[string]any{}); err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}

if err := d.render(w, s, "/device", view, level, map[string]any{}); err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}

s._save(d.Id, view, level)
}

func (d *device) showState(w http.ResponseWriter, r *http.Request) {
Expand Down
9 changes: 6 additions & 3 deletions pkg/device/device-linux.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,12 +22,15 @@ type deviceOS struct {
*http.ServeMux
templates *template.Template
layeredFS
views
viewsMu rwMutex
}

func (d *device) _buildOS() error {
var err error

d.ServeMux = http.NewServeMux()
d.views = make(views)

// Build device's layered FS. fs is stacked on top of
// deviceFs, so fs:foo.tmpl will override deviceFs:foo.tmpl,
Expand Down Expand Up @@ -254,12 +257,12 @@ func deviceRouteUp(id string, pkt *Packet) {
}
}

func deviceRenderPkt(w io.Writer, s *session, pkt *Packet) error {
//LogDebug("deviceRenderPkt", "sessionId", s.Id, "pkt", pkt)
func deviceRenderPkt(w io.Writer, sessionId string, pkt *Packet) error {
//LogDebug("deviceRenderPkt", "sessionId", sessionId, "pkt", pkt)
devicesMu.RLock()
defer devicesMu.RUnlock()
if d, ok := devices[pkt.Dst]; ok {
return d.renderPkt(w, s, pkt)
return d.renderPkt(w, sessionId, pkt)
}
return deviceNotFound(pkt.Dst)
}
Expand Down
1 change: 1 addition & 0 deletions pkg/device/device.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ type device struct {
Devicer `json:"-"`
stopChan chan struct{}
startup time.Time
stateMu mutex
flags
rwMutex
deviceOS
Expand Down
4 changes: 2 additions & 2 deletions pkg/device/docs/template-map.html
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ <h3>SESSION</h3>
session.tmpl
session-header.tmpl
session-view.tmpl
renderView device-xxx.tmpl (xxx = detail, overview, ...)
_renderView device-xxx.tmpl (xxx = detail, overview, ...)
device-detail.tmpl
<span class="text-red-500">body-detail.tmpl</span>
device-overview.tmpl
Expand Down Expand Up @@ -49,7 +49,7 @@ <h4>/status</h4>
<pre class="text-sm">
device.tmpl
device-header.tmpl
device-status.tmpl (renderTemplate device-status-xxx.tmpl)
device-status.tmpl (_renderTemplate device-status-xxx.tmpl)
device-status-sessions.tmpl
device-status-models.tmpl
device-status-devices.tmpl
Expand Down
6 changes: 3 additions & 3 deletions pkg/device/funcs.go
Original file line number Diff line number Diff line change
Expand Up @@ -95,8 +95,8 @@ func (d *device) baseFuncs() template.FuncMap {
"borderColor": d.borderColor,
"bodyColors": bodyColors,
"classOffline": d.classOffline,
"renderTemplate": d.renderTemplate,
"renderView": d.renderView,
"renderChildren": d.renderChildren,
"_renderTemplate": d._renderTemplate,
"_renderView": d._renderView,
"_renderChildren": d._renderChildren,
}
}
9 changes: 3 additions & 6 deletions pkg/device/handle-linux.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,9 @@ func (d *device) newPacketRoute(h packetHandler) http.Handler {

sessionId := r.Header.Get("session-id")

sessionsMu.RLock()
defer sessionsMu.RUnlock()

s, ok := sessions[sessionId]
if !ok || !s.connected() {
// Session expired, force full page refresh to start new session
if sessionExpired(sessionId) {
// Force full page refresh to start new session
LogDebug("Session expired, refreshing", "id", sessionId)
w.Header().Set("HX-Refresh", "true")
return
}
Expand Down
7 changes: 3 additions & 4 deletions pkg/device/handle.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,8 @@ func (h *PacketHandler[T]) cb(pkt *Packet) {
// PacketHandlers is a map of Handlers, keyed by path.
type PacketHandlers map[string]packetHandler

// _handle needs r/w lock so device cb handler can r/w device state
func (d *device) _handle(pkt *Packet) {
if d._isSet(flagOnline) || pkt.Path == "/online" {
if d.isSet(flagOnline) || pkt.Path == "/online" {
if handler, ok := d.PacketHandlers[pkt.Path]; ok {
LogDebug("Handling", "pkt", pkt)
handler.cb(pkt)
Expand All @@ -38,7 +37,7 @@ func (d *device) _handle(pkt *Packet) {
}

func (d *device) handle(pkt *Packet) {
d.Lock()
defer d.Unlock()
d.stateMu.Lock()
defer d.stateMu.Unlock()
d._handle(pkt)
}
4 changes: 3 additions & 1 deletion pkg/device/lock.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@ package device

//import "sync"

import "github.com/ietxaniz/delock"
import (
"github.com/ietxaniz/delock"
)

/*
type mutex struct {
Expand Down
Loading

0 comments on commit 4b18056

Please sign in to comment.