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",
"cx"
],
"gopls": {
"build.buildFlags": [
"-tags",
"synctrace"
]
},
"git.ignoreLimitWarning": true
}

View File

@ -45,6 +45,8 @@ type IPv6StaticConfig struct {
// NetworkConfig represents the complete network configuration for an interface
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"`
HTTPProxy null.String `json:"http_proxy,omitempty" validate_type:"proxy"`
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
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
@ -173,6 +176,31 @@ type NetworkConfigInterface interface {
IPv6Addresses() []IPAddress
}
// IsIPv6 returns true if the DHCP lease is for an IPv6 address
func (d *DHCPLease) IsIPv6() bool {
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 {
continue
}
if hasIPv4 && ip.To4() != nil {
filteredServers = append(filteredServers, server)
}

View File

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

View File

@ -18,21 +18,19 @@ type DHCPClient struct {
ifaceName string
logger *zerolog.Logger
client types.DHCPClient
clientType string
link netlink.Link
// Configuration
ipv4Enabled bool
ipv6Enabled bool
// State management
// stateManager *DHCPStateManager
// Callbacks
onLeaseChange func(lease *types.DHCPLease)
}
// 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 == "" {
return nil, fmt.Errorf("interface name cannot be empty")
}
@ -45,6 +43,7 @@ func NewDHCPClient(ctx context.Context, ifaceName string, logger *zerolog.Logger
ctx: ctx,
ifaceName: ifaceName,
logger: logger,
clientType: clientType,
}, nil
}
@ -70,10 +69,13 @@ func (dc *DHCPClient) SetOnLeaseChange(callback func(lease *types.DHCPLease)) {
}
func (dc *DHCPClient) initClient() (types.DHCPClient, error) {
if false {
switch dc.clientType {
case "jetdhcpc":
return dc.initJetDHCPC()
} else {
case "udhcpc":
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()).
Msg("DHCP lease changed")
// copy the lease to avoid race conditions
leaseCopy := *lease
// Notify callback
if dc.onLeaseChange != nil {
dc.onLeaseChange(lease)
dc.onLeaseChange(&leaseCopy)
}
}

View File

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

View File

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

View File

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

View File

@ -33,7 +33,9 @@ type Lease struct {
// ToDHCPLease converts a lease to a DHCP lease.
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.
@ -45,9 +47,9 @@ func fromNclient4Lease(l *nclient4.Lease, iface string) *Lease {
// only the fields that we need are set
lease.Routers = l.ACK.Router()
lease.IPAddress = l.ACK.YourIPAddr
lease.Netmask = net.IP(l.ACK.SubnetMask())
lease.Broadcast = l.ACK.BroadcastAddress()
// lease.MTU = int(resp.Options.Get(dhcpv4.OptionInterfaceMTU))
lease.NTPServers = l.ACK.NTPServers()

View File

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

View File

@ -6,7 +6,8 @@ package nmlite
import (
"context"
"fmt"
"sync"
"github.com/jetkvm/kvm/internal/sync"
"github.com/jetkvm/kvm/internal/logging"
"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.
func (l *Lease) ToDHCPLease() *types.DHCPLease {
return &l.DHCPLease
lease := &l.DHCPLease
lease.DHCPClient = "udhcpc"
return lease
}
// SetLeaseExpiry sets the lease expiry time.

View File

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

View File

@ -221,6 +221,13 @@ export default function DhcpLeaseCard({
</span>
</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>

View File

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