I'm reading the source code of MicroMDM, specifically the implementation of pollCommands (https://github.com/micromdm/micromdm/blob/5c70048c14592fc16c2107f1af1bf8e0d3a52e0b/platform/queue/queue.go#L223-L284):
func (db *Store) pollCommands(pubsub pubsub.PublishSubscriber) error {
commandEvents, err := pubsub.Subscribe(context.TODO(), "command-queue", command.CommandTopic)
if err != nil {
return errors.Wrapf(err,
"subscribing push to %s topic", command.CommandTopic)
}
go func() {
for {
select {
case event := <-commandEvents:
var ev command.Event
if err := command.UnmarshalEvent(event.Message, &ev); err != nil {
level.Info(db.logger).Log("msg", "unmarshal command event in queue", "err", err)
continue
}
cmd := new(DeviceCommand)
cmd.DeviceUDID = ev.DeviceUDID
byUDID, err := db.DeviceCommand(ev.DeviceUDID)
if err == nil && byUDID != nil {
cmd = byUDID
}
newPayload, err := plist.Marshal(ev.Payload)
if err != nil {
level.Info(db.logger).Log("msg", "marshal event payload", "err", err)
continue
}
newCmd := Command{
UUID: ev.Payload.CommandUUID,
Payload: newPayload,
}
cmd.Commands = append(cmd.Commands, newCmd)
if err := db.Save(cmd); err != nil {
level.Info(db.logger).Log("msg", "save command in db", "err", err)
continue
}
level.Info(db.logger).Log(
"msg", "queued event for device",
"device_udid", ev.DeviceUDID,
"command_uuid", ev.Payload.CommandUUID,
"request_type", ev.Payload.Command.RequestType,
)
cq := new(QueueCommandQueued)
cq.DeviceUDID = ev.DeviceUDID
cq.CommandUUID = ev.Payload.CommandUUID
msgBytes, err := MarshalQueuedCommand(cq)
if err != nil {
level.Info(db.logger).Log("msg", "marshal queued command", "err", err)
continue
}
if err := pubsub.Publish(context.TODO(), CommandQueuedTopic, msgBytes); err != nil {
level.Info(db.logger).Log("msg", "publish command to queued topic", "err", err)
}
}
}
}()
return nil
}
What puzzles me is why there is a select statement in the for loop with a single case. Would this code not be equivalent to
for {
event := <-commandEvents
var ev command.Event
...
}
It seems to me that the code could be simplified by doing away with the select statement?
Related
I have this API that scans for drivers' locations and send them via web-socket every 1 second. The issue is that the loop cannot be escaped when client disconnects. It seems it is alive for ever. I am using Gin with nhooyr websocket library.
var GetDriverLocations = func(c *gin.Context) {
wsoptions := websocket.AcceptOptions{InsecureSkipVerify: true}
wsconn, err := websocket.Accept(c.Writer, c.Request, &wsoptions)
if err != nil {
return
}
defer wsconn.Close(websocket.StatusInternalError, "the sky is falling")
driverLocation := &models.DriverLocation{}
ticker := time.NewTicker(time.Second)
defer ticker.Stop()
for {
select {
case <-ticker.C:
case <-c.Request.Context().Done():
fmt.Println("done") //this never gets printed
return
}
coords, err := driverLocation.GetDrivers()
if err != nil {
break
}
err = wsjson.Write(c.Request.Context(), wsconn, &coords)
if websocket.CloseStatus(err) == websocket.StatusNormalClosure {
break
}
if err != nil {
break
}
}
fmt.Println("conn ended") //this never gets printed
}
I also tried this loop but also has the same issue:
for range ticker.C{
coords, err := driverLocation.GetDrivers()
if err != nil {
break
}
err = wsjson.Write(c.Request.Context(), wsconn, &coords)
if websocket.CloseStatus(err) == websocket.StatusNormalClosure {
break
}
if err != nil {
break
}
}
Because the network connection is hijacked from the net/http server by the nhooyr websocket library, the context c.Request.Context() is not canceled until handler returns.
Call CloseRead to get a context that's canceled when the connection is closed. Use that context in the loop.
var GetDriverLocations = func(c *gin.Context) {
wsoptions := websocket.AcceptOptions{InsecureSkipVerify: true}
wsconn, err := websocket.Accept(c.Writer, c.Request, &wsoptions)
if err != nil {
return
}
defer wsconn.Close(websocket.StatusInternalError, "")
ctx := wsconn.CloseRead(c.Request.Context())
driverLocation := &models.DriverLocation{}
ticker := time.NewTicker(time.Second)
defer ticker.Stop()
for {
select {
case <-ticker.C:
case <-ctx.Done():
return
}
coords, err := driverLocation.GetDrivers()
if err != nil {
break
}
err = wsjson.Write(c.Request.Context(), wsconn, &coords)
if err != nil {
break
}
}
}
panic: runtime error: invalid memory address or nil pointer dereference
when i add
statusCOD, err := c.route.FindStatusCODByOrigin(ctx, req.Origin)
if err != nil {
if err != sql.ErrNoRows {
ddlogger.Log(ctx, span, "Find status cod destination and cod origin", err)
errChan <- err
return
}
}
if statusCOD != nil {
IsCODOrigin = statusCOD.IsCODOrigin
IsCODDestination = statusCOD.IsCODDestination
}
in this func
for i, v := range detailShipments {
var dtPackage repo.PackageBaseModel
go func(idx int, vShipment repo.ShipmentDetailBaseModel, dataShipmentNew repo.ShipmentCODCreateModel) {
defer wg1.Done()
randomID := commonString.RandomWithCustomCharList(c.config.ShipmentCODIDRandom, c.config.ShipmentIDCharlist)
shipmentID := fmt.Sprintf("%s%s", prefix, randomID)
dataShipmentNew.ShipmentBaseModel.ShipmentID = strings.ToUpper(shipmentID)
dataShipmentNew.ShipmentDetailBaseModel = vShipment
var commodityName string
sCategory, err := c.shipmentCategoryRepo.FindOneShipmentCategoryByID(ctx, vShipment.ShipmentCategoryID.Int64)
if err != err && err != sql.ErrNoRows {
ddlogger.Log(ctx, span, "shipmentService-CreateShipmentCOD "+shipmentID, " Failed shipmentCategoryRepo.FindOneShipmentCategoryByID", err)
} else {
if sCategory != nil {
commodityName = sCategory.CommodityName.String
}
}
req := &repo.TariffRequestModelV2{
Origin: form.DetailSender.Origin,
Destination: vShipment.Destination,
Weight: vShipment.GrossWeight / 1000,
Commodity: commodityName,
GoodsValue: int64(vShipment.GoodValues.Float64),
IsInsurance: vShipment.IsInsurance.Bool,
Product: vShipment.ProductType,
Width: vShipment.DimensionWidth.Float64,
Height: vShipment.DimensionHeight.Float64,
Length: vShipment.DimensionLength.Float64,
IsCOD: true,
CODValue: vShipment.CODValue.Float64,
}
tariff, err := c.coreSystemRepo.CheckTariffV3(ctx, req)
if err != nil {
ddlogger.Log(ctx, span, "[shipmentService-CreateShipmentCOD [coreSystemRepo.CheckTariffV3]]", err)
errChan <- err
return
}
statusCOD, err := c.route.FindStatusCODByOrigin(ctx, req.Origin)
if err != nil {
if err != sql.ErrNoRows {
ddlogger.Log(ctx, span, "Find status cod destination and cod origin", err)
errChan <- err
return
}
}
if statusCOD != nil {
IsCODOrigin = statusCOD.IsCODOrigin
IsCODDestination = statusCOD.IsCODDestination
}
................
}
wg1.Wait()
close(shipmentChan)
close(errChan)
close(amountChan)
It looks like you just created the pointer, and it doesn't point to any memory spaces, you can check all the variables in your new code to find it. e.g. ctx
Most cleanup functions, especially those related to the IO operations, return an error, and normally we'd prefer to defer their execution in case if we'd not forget to call them when we're done with acquired resources. For example, at some point in the code we might write something like this:
var r *SomeResource
var err error
if r, err = Open(/* parameters */); err != nil {
return nil, err
}
defer r.Close() // This might return an error
It seems that if Close function returns an error, it'll be ignored. How can we gently process the returned error from such a function?
Using defer with a func() {}() like so.
var r *SomeResource
var err error
if r, err = Open(/* parameters */); err != nil {
return nil, err
}
defer func() {
if err = r.Close(); err != nil {
fmt.Printf("ERROR: %v", err)
}
}()
Fail gracefully with an error. Report the first error. Don't overwrite earlier errors. For example,
package main
import (
"fmt"
"os"
)
func demo() (name string, err error) {
filename := `test.file`
f, err := os.Open(filename)
if err != nil {
return "", err
}
defer func() {
e := f.Close()
if e != nil {
if err == nil {
err = e
}
}
}()
// do someting with the file
name = f.Name()
fi, err := f.Stat()
if err != nil {
return name, err
}
if fi.Size() == 0 {
err = fmt.Errorf("%s: empty file", filename)
return name, err
}
return name, err
}
func main() {
name, err := demo()
fmt.Println(name, err)
}
We can handle this in ways like:
way-1:
func myFn() error {
var err error
if r, err = Open(/* parameters */); err != nil {
return nil, err
}
defer func() {
if cErr = r.Close(); cErr != nil {
err = cErr
}
}()
return err
}
way-2:
func myFn() error {
var err error
if r, err = Open(/* parameters */); err != nil {
return nil, err
}
defer func() {
if cErr = r.Close(); cErr != nil {
// we can log the error
// or
// whatever we want to do
}
}()
return err
}
I have also find a nice blog on this topic, i mean handling error when defer func returns an error. Check here https://blog.learngoprogramming.com/5-gotchas-of-defer-in-go-golang-part-iii-36a1ab3d6ef1.
OS: windows/7/8/8.1/10 32bit
I have one question. How to create a service that would work like autorun?
Most applications install themselves in autorun through the registry or through C:\Users\Anon\AppData\Roaming\Microsoft\Windows\Start Menu\Programs\Startup. But there are those that are installing through the services, or rather as a service.
I have a code:
package main
import (
"fmt"
"strings"
"time"
"syscall"
"golang.org/x/sys/windows/svc"
"golang.org/x/sys/windows/svc/mgr"
"golang.org/x/sys/windows/svc/debug"
"log"
"os"
"path/filepath"
"golang.org/x/sys/windows/svc/eventlog"
)
var elog debug.Log
type myservice struct{}
func (m *myservice) Execute(args []string, r <-chan svc.ChangeRequest, changes chan<- svc.Status) (ssec bool, errno uint32) {
const cmdsAccepted = svc.AcceptStop | svc.AcceptShutdown | svc.AcceptPauseAndContinue
changes <- svc.Status{State: svc.StartPending}
fasttick := time.Tick(500 * time.Millisecond)
slowtick := time.Tick(2 * time.Second)
tick := fasttick
changes <- svc.Status{State: svc.Running, Accepts: cmdsAccepted}
elog.Info(1, strings.Join(args, "-"))
loop:
for {
select {
case <-tick:
beep()
elog.Info(1, "beep")
case c := <-r:
switch c.Cmd {
case svc.Interrogate:
changes <- c.CurrentStatus
// Testing deadlock from https://code.google.com/p/winsvc/issues/detail?id=4
time.Sleep(100 * time.Millisecond)
changes <- c.CurrentStatus
case svc.Stop, svc.Shutdown:
break loop
case svc.Pause:
changes <- svc.Status{State: svc.Paused, Accepts: cmdsAccepted}
tick = slowtick
case svc.Continue:
changes <- svc.Status{State: svc.Running, Accepts: cmdsAccepted}
tick = fasttick
default:
elog.Error(1, fmt.Sprintf("unexpected control request #%d", c))
}
}
}
changes <- svc.Status{State: svc.StopPending}
return
}
func runService(name string, isDebug bool) {
var err error
if isDebug {
elog = debug.New(name)
} else {
elog, err = eventlog.Open(name)
if err != nil {
return
}
}
defer elog.Close()
elog.Info(1, fmt.Sprintf("starting %s service", name))
run := svc.Run
if isDebug {
run = debug.Run
}
err = run(name, &myservice{})
if err != nil {
elog.Error(1, fmt.Sprintf("%s service failed: %v", name, err))
return
}
elog.Info(1, fmt.Sprintf("%s service stopped", name))
}
func startService(name string) error {
m, err := mgr.Connect()
if err != nil {
return err
}
defer m.Disconnect()
s, err := m.OpenService(name)
if err != nil {
return fmt.Errorf("could not access service: %v", err)
}
defer s.Close()
err = s.Start("is", "auto-started")
if err != nil {
return fmt.Errorf("could not start service: %v", err)
}
return nil
}
func controlService(name string, c svc.Cmd, to svc.State) error {
m, err := mgr.Connect()
if err != nil {
return err
}
defer m.Disconnect()
s, err := m.OpenService(name)
if err != nil {
return fmt.Errorf("could not access service: %v", err)
}
defer s.Close()
status, err := s.Control(c)
if err != nil {
return fmt.Errorf("could not send control=%d: %v", c, err)
}
timeout := time.Now().Add(10 * time.Second)
for status.State != to {
if timeout.Before(time.Now()) {
return fmt.Errorf("timeout waiting for service to go to state=%d", to)
}
time.Sleep(300 * time.Millisecond)
status, err = s.Query()
if err != nil {
return fmt.Errorf("could not retrieve service status: %v", err)
}
}
return nil
}
func main() {
const svcName = "Best Service"
isIntSess, err := svc.IsAnInteractiveSession()
if err != nil {
log.Fatalf("failed to determine if we are running in an interactive session: %v", err)
}
if !isIntSess {
runService(svcName, false)
return
}
/*err = controlService(svcName, svc.Stop, svc.Stopped)
err = removeService(svcName)*/
err = installService(svcName, "Best Service")
runService(svcName, true)
if err != nil {
log.Fatalf("failed to %s: %v", svcName, err)
}
return
}
func exePath() (string, error) {
prog := os.Args[0]
p, err := filepath.Abs(prog)
if err != nil {
return "", err
}
fi, err := os.Stat(p)
if err == nil {
if !fi.Mode().IsDir() {
return p, nil
}
err = fmt.Errorf("%s is directory", p)
}
if filepath.Ext(p) == "" {
p += ".exe"
fi, err := os.Stat(p)
if err == nil {
if !fi.Mode().IsDir() {
return p, nil
}
err = fmt.Errorf("%s is directory", p)
}
}
return "", err
}
func installService(name, desc string) error {
exepath, err := exePath()
if err != nil {
return err
}
m, err := mgr.Connect()
if err != nil {
return err
}
defer m.Disconnect()
s, err := m.OpenService(name)
if err == nil {
s.Close()
return fmt.Errorf("service %s already exists", name)
}
s, err = m.CreateService(name, exepath, mgr.Config{DisplayName: desc, Description: "BB service"}, "is", "auto-started")
if err != nil {
return err
}
defer s.Close()
err = eventlog.InstallAsEventCreate(name, eventlog.Error|eventlog.Warning|eventlog.Info)
if err != nil {
s.Delete()
return fmt.Errorf("SetupEventLogSource() failed: %s", err)
}
return nil
}
func removeService(name string) error {
m, err := mgr.Connect()
if err != nil {
return err
}
defer m.Disconnect()
s, err := m.OpenService(name)
if err != nil {
return fmt.Errorf("service %s is not installed", name)
}
defer s.Close()
err = s.Delete()
if err != nil {
return err
}
err = eventlog.Remove(name)
if err != nil {
return fmt.Errorf("RemoveEventLogSource() failed: %s", err)
}
return nil
}
var (
beepFunc = syscall.MustLoadDLL("user32.dll").MustFindProc("MessageBeep")
)
func beep() {
beepFunc.Call(0xffffffff)
}
Application is installed and every time I exit the application the service stops. I need that even after restarting the PC the service worked and the application started. How can I do it?
maybe it's not actual but during the creation of the service you should extend and pass Config
mgr.Config{DisplayName: desc, StartType: mgr.StartAutomatic}
like here:
s, err = m.CreateService(name, exepath, mgr.Config{DisplayName: desc, StartType: mgr.StartAutomatic}, "is", "auto-started")
if err != nil {
return err
}
Here you can find all necessary constants and functions:
https://github.com/golang/sys/blob/master/windows/svc/mgr/config.go
On Windows 10 go to Task Scheduler > Task Scheduler Library > Create Basic Task > Trigger: when the computer starts > Action: Start a program.
When running the task, use the following user account: SYSTEM.
There is a package in the standard library that does this:
https://godoc.org/golang.org/x/sys/windows/svc
example: https://github.com/golang/sys/tree/master/windows/svc/example
I would like to decompress tar-gz file by golang.
err := DecompressTarGz('xxx.tar.gz', '/Users/foobarbuzz/')
Use compress/gzip in combination with archive/tar or use os/exec to call tar and gzip directly if you don't like to implement all of that in Go.
I was looking for an answer to this one too. Implemented something that should work.
func Decompress(targetdir string, reader io.ReadCloser) error {
gzReader, err := gzip.NewReader(reader)
if err != nil {
return err
}
defer gzReader.Close()
tarReader := tar.NewReader(gzReader)
for {
header, err := tarReader.Next()
if err == io.EOF {
break
} else if err != nil {
return err
}
target := path.Join(targetdir, header.Name)
switch header.Typeflag {
case tar.TypeDir:
err = os.MkdirAll(target, os.FileMode(header.Mode))
if err != nil {
return err
}
setAttrs(target, header)
break
case tar.TypeReg:
w, err := os.Create(target)
if err != nil {
return err
}
_, err = io.Copy(w, tarReader)
if err != nil {
return err
}
w.Close()
setAttrs(target, header)
break
default:
log.Printf("unsupported type: %v", header.Typeflag)
break
}
}
return nil
}
func setAttrs(target string, header *tar.Header) {
os.Chmod(target, os.FileMode(header.Mode))
os.Chtimes(target, header.AccessTime, header.ModTime)
}