use sync trace to track mutexes to make deadlock analysis easier

This commit is contained in:
Siyuan 2025-10-07 23:12:20 +00:00
parent b04b148a4b
commit aef26459d3
15 changed files with 92 additions and 33 deletions

View File

@ -3,5 +3,11 @@
"cva", "cva",
"cx" "cx"
], ],
"gopls": {
"build.buildFlags": [
"-tags",
"synctrace"
]
},
"git.ignoreLimitWarning": true "git.ignoreLimitWarning": true
} }

View File

@ -45,6 +45,8 @@ type IPv6StaticConfig struct {
// NetworkConfig represents the complete network configuration for an interface // NetworkConfig represents the complete network configuration for an interface
type NetworkConfig struct { type NetworkConfig struct {
DHCPClient null.String `json:"dhcp_client,omitempty" one_of:"jetdhcpc,udhcpc" default:"jetdhcpc"`
Hostname null.String `json:"hostname,omitempty" validate_type:"hostname"` Hostname null.String `json:"hostname,omitempty" validate_type:"hostname"`
HTTPProxy null.String `json:"http_proxy,omitempty" validate_type:"proxy"` HTTPProxy null.String `json:"http_proxy,omitempty" validate_type:"proxy"`
Domain null.String `json:"domain,omitempty" validate_type:"hostname"` Domain null.String `json:"domain,omitempty" validate_type:"hostname"`
@ -145,6 +147,7 @@ type DHCPLease struct {
LeaseExpiry *time.Time `json:"lease_expiry,omitempty"` // The expiry time of the lease LeaseExpiry *time.Time `json:"lease_expiry,omitempty"` // The expiry time of the lease
InterfaceName string `json:"interface_name,omitempty"` // The name of the interface InterfaceName string `json:"interface_name,omitempty"` // The name of the interface
DHCPClient string `json:"dhcp_client,omitempty"` // The DHCP client that obtained the lease
} }
// InterfaceState represents the current state of a network interface // InterfaceState represents the current state of a network interface
@ -173,6 +176,31 @@ type NetworkConfigInterface interface {
IPv6Addresses() []IPAddress IPv6Addresses() []IPAddress
} }
// IsIPv6 returns true if the DHCP lease is for an IPv6 address
func (d *DHCPLease) IsIPv6() bool { func (d *DHCPLease) IsIPv6() bool {
return d.IPAddress.To4() == nil return d.IPAddress.To4() == nil
} }
// IPMask returns the IP mask for the DHCP lease
func (d *DHCPLease) IPMask() net.IPMask {
if d.IsIPv6() {
// TODO: not implemented
return nil
}
mask := net.ParseIP(d.Netmask.String())
return net.IPv4Mask(mask[12], mask[13], mask[14], mask[15])
}
// IPNet returns the IP net for the DHCP lease
func (d *DHCPLease) IPNet() *net.IPNet {
if d.IsIPv6() {
// TODO: not implemented
return nil
}
return &net.IPNet{
IP: d.IPAddress,
Mask: d.IPMask(),
}
}

View File

@ -62,6 +62,7 @@ func (t *TimeSync) filterNTPServers(ntpServers []string) ([]string, error) {
if ip == nil { if ip == nil {
continue continue
} }
if hasIPv4 && ip.To4() != nil { if hasIPv4 && ip.To4() != nil {
filteredServers = append(filteredServers, server) filteredServers = append(filteredServers, server)
} }

View File

@ -169,6 +169,9 @@ func (t *TimeSync) doTimeSync() {
} }
func (t *TimeSync) Sync() error { func (t *TimeSync) Sync() error {
t.syncLock.Lock()
defer t.syncLock.Unlock()
var ( var (
now *time.Time now *time.Time
offset *time.Duration offset *time.Duration

View File

@ -14,25 +14,23 @@ import (
// DHCPClient wraps the dhclient package for use in the network manager // DHCPClient wraps the dhclient package for use in the network manager
type DHCPClient struct { type DHCPClient struct {
ctx context.Context ctx context.Context
ifaceName string ifaceName string
logger *zerolog.Logger logger *zerolog.Logger
client types.DHCPClient client types.DHCPClient
link netlink.Link clientType string
link netlink.Link
// Configuration // Configuration
ipv4Enabled bool ipv4Enabled bool
ipv6Enabled bool ipv6Enabled bool
// State management
// stateManager *DHCPStateManager
// Callbacks // Callbacks
onLeaseChange func(lease *types.DHCPLease) onLeaseChange func(lease *types.DHCPLease)
} }
// NewDHCPClient creates a new DHCP client // NewDHCPClient creates a new DHCP client
func NewDHCPClient(ctx context.Context, ifaceName string, logger *zerolog.Logger) (*DHCPClient, error) { func NewDHCPClient(ctx context.Context, ifaceName string, logger *zerolog.Logger, clientType string) (*DHCPClient, error) {
if ifaceName == "" { if ifaceName == "" {
return nil, fmt.Errorf("interface name cannot be empty") return nil, fmt.Errorf("interface name cannot be empty")
} }
@ -42,9 +40,10 @@ func NewDHCPClient(ctx context.Context, ifaceName string, logger *zerolog.Logger
} }
return &DHCPClient{ return &DHCPClient{
ctx: ctx, ctx: ctx,
ifaceName: ifaceName, ifaceName: ifaceName,
logger: logger, logger: logger,
clientType: clientType,
}, nil }, nil
} }
@ -70,10 +69,13 @@ func (dc *DHCPClient) SetOnLeaseChange(callback func(lease *types.DHCPLease)) {
} }
func (dc *DHCPClient) initClient() (types.DHCPClient, error) { func (dc *DHCPClient) initClient() (types.DHCPClient, error) {
if false { switch dc.clientType {
case "jetdhcpc":
return dc.initJetDHCPC() return dc.initJetDHCPC()
} else { case "udhcpc":
return dc.initUDHCPC() return dc.initUDHCPC()
default:
return nil, fmt.Errorf("invalid client type: %s", dc.clientType)
} }
} }
@ -204,8 +206,11 @@ func (dc *DHCPClient) handleLeaseChange(lease *types.DHCPLease, isIPv6 bool) {
Str("ip", lease.IPAddress.String()). Str("ip", lease.IPAddress.String()).
Msg("DHCP lease changed") Msg("DHCP lease changed")
// copy the lease to avoid race conditions
leaseCopy := *lease
// Notify callback // Notify callback
if dc.onLeaseChange != nil { if dc.onLeaseChange != nil {
dc.onLeaseChange(lease) dc.onLeaseChange(&leaseCopy)
} }
} }

View File

@ -6,7 +6,8 @@ import (
"os" "os"
"os/exec" "os/exec"
"strings" "strings"
"sync"
"github.com/jetkvm/kvm/internal/sync"
"github.com/rs/zerolog" "github.com/rs/zerolog"
"golang.org/x/net/idna" "golang.org/x/net/idna"

View File

@ -4,9 +4,11 @@ import (
"context" "context"
"fmt" "fmt"
"net" "net"
"sync"
"time" "time"
"github.com/jetkvm/kvm/internal/sync"
"github.com/jetkvm/kvm/internal/confparser" "github.com/jetkvm/kvm/internal/confparser"
"github.com/jetkvm/kvm/internal/logging" "github.com/jetkvm/kvm/internal/logging"
"github.com/jetkvm/kvm/internal/network/types" "github.com/jetkvm/kvm/internal/network/types"
@ -78,7 +80,7 @@ func NewInterfaceManager(ctx context.Context, ifaceName string, config *types.Ne
} }
// create the dhcp client // create the dhcp client
im.dhcpClient, err = NewDHCPClient(ctx, ifaceName, &scopedLogger) im.dhcpClient, err = NewDHCPClient(ctx, ifaceName, &scopedLogger, config.DHCPClient.String)
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to create DHCP client: %w", err) return nil, fmt.Errorf("failed to create DHCP client: %w", err)
} }
@ -562,7 +564,6 @@ func (im *InterfaceManager) monitorInterfaceState() {
defer im.wg.Done() defer im.wg.Done()
im.logger.Debug().Msg("monitoring interface state") im.logger.Debug().Msg("monitoring interface state")
// TODO: use netlink subscription instead of polling // TODO: use netlink subscription instead of polling
ticker := time.NewTicker(5 * time.Second) ticker := time.NewTicker(5 * time.Second)
defer ticker.Stop() defer ticker.Stop()
@ -574,7 +575,6 @@ func (im *InterfaceManager) monitorInterfaceState() {
case <-im.stopCh: case <-im.stopCh:
return return
case <-ticker.C: case <-ticker.C:
im.logger.Debug().Msg("checking interface state")
if err := im.updateInterfaceState(); err != nil { if err := im.updateInterfaceState(); err != nil {
im.logger.Error().Err(err).Msg("failed to update interface state") im.logger.Error().Err(err).Msg("failed to update interface state")
} }
@ -727,12 +727,9 @@ func (im *InterfaceManager) applyDHCPLease(lease *types.DHCPLease) error {
// convertDHCPLeaseToIPv4Config converts a DHCP lease to IPv4Config // convertDHCPLeaseToIPv4Config converts a DHCP lease to IPv4Config
func (im *InterfaceManager) convertDHCPLeaseToIPv4Config(lease *types.DHCPLease) *types.IPAddress { func (im *InterfaceManager) convertDHCPLeaseToIPv4Config(lease *types.DHCPLease) *types.IPAddress {
mask := lease.Netmask ipNet := lease.IPNet()
if ipNet == nil {
// Create IPNet from IP and netmask return nil
ipNet := &net.IPNet{
IP: lease.IPAddress,
Mask: net.IPv4Mask(mask[12], mask[13], mask[14], mask[15]),
} }
// Create IPv4Address // Create IPv4Address

View File

@ -6,9 +6,11 @@ import (
"fmt" "fmt"
"net" "net"
"slices" "slices"
"sync"
"time" "time"
"github.com/jetkvm/kvm/internal/sync"
"github.com/go-co-op/gocron/v2" "github.com/go-co-op/gocron/v2"
"github.com/insomniacslk/dhcp/dhcpv4" "github.com/insomniacslk/dhcp/dhcpv4"
"github.com/insomniacslk/dhcp/dhcpv6" "github.com/insomniacslk/dhcp/dhcpv6"

View File

@ -33,7 +33,9 @@ type Lease struct {
// ToDHCPLease converts a lease to a DHCP lease. // ToDHCPLease converts a lease to a DHCP lease.
func (l *Lease) ToDHCPLease() *types.DHCPLease { func (l *Lease) ToDHCPLease() *types.DHCPLease {
return &l.DHCPLease lease := &l.DHCPLease
lease.DHCPClient = "jetdhcpc"
return lease
} }
// fromNclient4Lease creates a lease from a nclient4.Lease. // fromNclient4Lease creates a lease from a nclient4.Lease.
@ -45,9 +47,9 @@ func fromNclient4Lease(l *nclient4.Lease, iface string) *Lease {
// only the fields that we need are set // only the fields that we need are set
lease.Routers = l.ACK.Router() lease.Routers = l.ACK.Router()
lease.IPAddress = l.ACK.YourIPAddr lease.IPAddress = l.ACK.YourIPAddr
lease.Netmask = net.IP(l.ACK.SubnetMask()) lease.Netmask = net.IP(l.ACK.SubnetMask())
lease.Broadcast = l.ACK.BroadcastAddress() lease.Broadcast = l.ACK.BroadcastAddress()
// lease.MTU = int(resp.Options.Get(dhcpv4.OptionInterfaceMTU))
lease.NTPServers = l.ACK.NTPServers() lease.NTPServers = l.ACK.NTPServers()

View File

@ -10,9 +10,10 @@ import (
"path" "path"
"strconv" "strconv"
"strings" "strings"
"sync"
"time" "time"
"github.com/jetkvm/kvm/internal/sync"
"github.com/jetkvm/kvm/internal/network/types" "github.com/jetkvm/kvm/internal/network/types"
"github.com/rs/zerolog" "github.com/rs/zerolog"
"github.com/vishvananda/netlink" "github.com/vishvananda/netlink"

View File

@ -6,7 +6,8 @@ package nmlite
import ( import (
"context" "context"
"fmt" "fmt"
"sync"
"github.com/jetkvm/kvm/internal/sync"
"github.com/jetkvm/kvm/internal/logging" "github.com/jetkvm/kvm/internal/logging"
"github.com/jetkvm/kvm/internal/network/types" "github.com/jetkvm/kvm/internal/network/types"

View File

@ -40,7 +40,9 @@ func (l *Lease) ToJSON() string {
// ToDHCPLease converts a lease to a DHCP lease. // ToDHCPLease converts a lease to a DHCP lease.
func (l *Lease) ToDHCPLease() *types.DHCPLease { func (l *Lease) ToDHCPLease() *types.DHCPLease {
return &l.DHCPLease lease := &l.DHCPLease
lease.DHCPClient = "udhcpc"
return lease
} }
// SetLeaseExpiry sets the lease expiry time. // SetLeaseExpiry sets the lease expiry time.

View File

@ -6,9 +6,11 @@ import (
"os" "os"
"path/filepath" "path/filepath"
"reflect" "reflect"
"sync"
"time" "time"
"github.com/jetkvm/kvm/internal/sync"
"github.com/fsnotify/fsnotify" "github.com/fsnotify/fsnotify"
"github.com/jetkvm/kvm/internal/network/types" "github.com/jetkvm/kvm/internal/network/types"
"github.com/rs/zerolog" "github.com/rs/zerolog"

View File

@ -221,6 +221,13 @@ export default function DhcpLeaseCard({
</span> </span>
</div> </div>
)} )}
{networkState?.dhcp_lease?.dhcp_client && (
<div className="flex justify-between border-t border-slate-800/10 pt-2 dark:border-slate-300/20">
<span className="text-sm text-slate-600 dark:text-slate-400">DHCP Client</span>
<span className="text-sm font-medium">{networkState?.dhcp_lease?.dhcp_client}</span>
</div>
)}
</div> </div>
</div> </div>
</div> </div>

View File

@ -690,6 +690,7 @@ export interface DhcpLease {
message?: string; message?: string;
tftp?: string; tftp?: string;
bootfile?: string; bootfile?: string;
dhcp_client?: string;
} }
export interface IPv6Address { export interface IPv6Address {