I am trying to write a go app that will monitor the status of a server application I run in windows. The application will run for roughly 16 hours before throwing out the following error: (Small snippet)
fatal error: too many callback functions
goroutine 137 [running]:
runtime.throw(0xc4c0a1, 0x1b)
H:/Program Files/Go/src/runtime/panic.go:1117 +0x79 fp=0xc000639d30 sp=0xc000639d00 pc=0x899379
syscall.compileCallback(0xbd18a0, 0xc00041fce0, 0x1, 0x0)
H:/Program Files/Go/src/runtime/syscall_windows.go:201 +0x5e5 fp=0xc000639e28 sp=0xc000639d30 pc=0x8c90e5
syscall.NewCallback(...)
H:/Program Files/Go/src/syscall/syscall_windows.go:177
main.FindWindow(0xc47278, 0x13, 0xc000639f50, 0x2, 0x2)
I have two files. One is the file that is calling a bunch of Windows API stuff, and one is a goroutine that is being performed every 30 seconds to get an update.
I am fairly new to go, especially in windows related development, so I am struggling to find the issue and how to prevent it.
Here is the main file (test example).
func main() {
go updateServerStats()
select {}
}
func ServerStats() {
serverStatsTicker := time.NewTicker(30 * time.Second)
for range serverStatsTicker.C {
serverRunning, serverHung, err := ServerHangCheck()
if err != nil {
ErrorLogger.Println("Server Check Error: ", err)
}
if serverHung {
fmt.Println("Server is hung")
}
}
}
Here is the primary callback/windows file. Its very much still a very rough, very work in progress. Something I found and modified from go playground.
package main
import (
"fmt"
"syscall"
"unsafe"
)
var (
user32 = syscall.MustLoadDLL("user32.dll")
procEnumWindows = user32.MustFindProc("EnumWindows")
procGetWindowTextW = user32.MustFindProc("GetWindowTextW")
procIsHungAppWindow = user32.MustFindProc("IsHungAppWindow")
)
//EnumWindows iterates over each window to be used for callbacks
func EnumWindows(enumFunc uintptr, lparam uintptr) (err error) {
r1, _, e1 := syscall.Syscall(procEnumWindows.Addr(), 2, uintptr(enumFunc), uintptr(lparam), 0)
if r1 == 0 {
if e1 != 0 {
err = error(e1)
} else {
err = syscall.EINVAL
}
}
return
}
//GetWindowText gets the description of the Window, which is the "Text" of the window.
func GetWindowText(hwnd syscall.Handle, str *uint16, maxCount int32) (len int32, err error) {
r0, _, e1 := syscall.Syscall(procGetWindowTextW.Addr(), 3, uintptr(hwnd), uintptr(unsafe.Pointer(str)), uintptr(maxCount))
len = int32(r0)
if len == 0 {
if e1 != 0 {
err = error(e1)
} else {
err = syscall.EINVAL
}
}
return
}
//IsHungAppWindow uses the IsHungAppWindow to see if Windows has been getting responses.
func IsHungAppWindow(hwnd syscall.Handle) (ishung bool, err error) {
r2, _, err := syscall.Syscall(procIsHungAppWindow.Addr(), 2, uintptr(hwnd), 0, 0)
if r2 == 1{
return true, err
}
return false, err
}
//FindWindow uses EnumWindows with a callback to GetWindowText, and if matches given Title, checks if its hung, and returns state.
func FindWindow(title string) (bool ,bool, error) {
var hwnd syscall.Handle
var isHung bool = false
cb := syscall.NewCallback(func(h syscall.Handle, p uintptr) uintptr {
b := make([]uint16, 200)
_, err := GetWindowText(h, &b[0], int32(len(b)))
if err != nil {
// ignore the error
return 1 // continue enumeration
}
if syscall.UTF16ToString(b) == title {
// note the window
isHung, _ = IsHungAppWindow(h)
hwnd = h
return 0 // stop enumeration
}
return 1 // continue enumeration
})
EnumWindows(cb, 0)
if hwnd == 0 {
return false, false, fmt.Errorf("DCS Not Found")
}
if isHung == true {
return true, isHung, fmt.Errorf("DCS Is Running But Hung")
}
return true, isHung, nil
}
//ServerHangCheck checks the server to see if the window is hung or process is running.
func ServerHangCheck() (bool, bool, error) {
const title = "server_application"
running, hung, err := FindWindow(title)
return running, hung, err
}
Looking at syscall.NewCallback, we find that it's actually implemented via runtime/syscall_windows.go as the function compileCallback:
func NewCallback(fn interface{}) uintptr {
return compileCallback(fn, true)
}
Looking at the runtime/syscall_windows.go code we find that it has a fixed size table of all registered Go callbacks. This code varies a lot between Go releases so it's not too productive to delve any further here. However, there's one thing that is clear: the code checks to see if the callback function is already registered, and if so, re-uses it. So a single callback function occupies one table slot, but adding multiple functions will eventually use up all the table slots and result in the fatal error that you encountered.
You asked in a comment (the comments popped up while I was writing this):
Would I need to reuse it, or can I "close" the original? – Mallachar 7 mins ago
You cannot close out the original. There's an actual table of functions elsewhere in memory; a registered callback uses up a slot in this table, and slots are never released.
Per the advice of torek I got a work around.
I created a global variable
var callbacker uintptr
Then then I created a function which is called by init
func init() {
callbacker = syscall.NewCallback(CallBackCreator)
}
func CallBackCreator(h syscall.Handle, p uintptr) uintptr {
hwnd = 0
b := make([]uint16, 200)
_, err := GetWindowText(h, &b[0], int32(len(b)))
if err != nil {
// ignore the error
return 1 // continue enumeration
}
if syscall.UTF16ToString(b) == title {
// note the window
isHung, _ = IsHungAppWindow(h)
hwnd = h
return 0 // stop enumeration
}
return 1 // continue enumeration
}
which is now called by the function
EnumWindows(callbacker, 0)
Likely not the most "Go" appropriate way, but I am now making better progress.
Related
This project is made to receive POST routes that will finally count as access to later write to a database. The intuition is to save interaction with the database of another project in production. I decided to do it in go, but I'm new to the language and I'm struggling to understand. I'm trying to make it so that there is no loss or that there are more accesses.
The project basically consists of a controller, a service and two models, just enough to meet the need for which it was created. In my controller I have the function that will be responsible for receiving the POST.
controllers/views.go:
func StoreViews(c *fiber.Ctx) error {
var songview models.SongView
err := c.BodyParser(&songview)
if err != nil {
return c.Status(403).JSON(fiber.Map{
"errors": fiber.Map{"request": err.Error()},
})
}
songview.Date = time.Now()
errs := utils.ValidateStruct(songview)
if len(errs) > 0 {
return c.Status(403).JSON(map[string]interface{}{"errors": errs})
}
go services.StoreViews(songview)
return c.SendStatus(fiber.StatusOK)
}
To handle the received data I made these three functions in my service:
services/views.go
var (
StoreViewsMap = make(map[string]*models.SongView)
StoreControl sync.RWMutex
)
func StoreViews(sview models.SongView) bool {
nameKey := strconv.Itoa(int(sview.SongId)) + sview.Lang + sview.Date.Format("2006-01-02")
songview := getSongView(nameKey)
initSongView(nameKey, songview, sview)
return true
}
func getSongView(name string) *models.SongView {
StoreControl.RLock()
defer StoreControl.RUnlock()
return StoreViewsMap[name]
}
func initSongView(name string, songview *models.SongView, sview models.SongView) bool {
StoreControl.Lock()
defer StoreControl.Unlock()
if songview == nil {
insert := models.SongView{
SongId: sview.SongId,
Lang: sview.Lang,
Date: sview.Date,
Views: 0,
}
songViewNew := &insert // see if & is needed
StoreViewsMap[name] = songViewNew
} else {
songview.Views = songview.Views + 1
}
return true
}
I tried to implement RWMutex to get it to do everything without overlapping anything, but it's not working as it should, sometimes it disappears with views, other times it rescues "songview" in the getSongView function wrongly, among several other problems that I found modifying and reviewing my code. The current code is not in the version that I managed to get closer to the expected result, but I didn't save this version so I decided to bring the current code to exemplify what I'm facing.
I would like you to help me understand how I can deal with several concurrent requests disputing, how I can interact with the data in the best possible way and if there is an error in the use of a pointer I am open to understand. To simulate a POST "attack" to my code I'm using this code in another main.go I made for this test.
var limit int = 10
func main() {
channel := make(chan string)
for i := 0; i < limit; i++ {
go func(i int) {
post("http://localhost:3000/views/store", "lang=pt&song_id=296", i)
channel <- "ok"
}(i)
go func(i int) {
post("http://localhost:3000/views/store", "lang=en&song_id=3016", i)
channel <- "ok"
}(i)
go func(i int) {
post("http://localhost:3000/views/store", "lang=pt&song_id=3016", i)
channel <- "ok"
}(i)
}
for i := 0; i < limit*3; i++ {
<-channel
}
}
func post(url string, json string, index int) {
payload := strings.NewReader(json)
client := &http.Client{}
req, err := http.NewRequest("POST", url, payload)
if err != nil {
fmt.Println(err)
return
}
req.Header.Add("Content-Type", "application/x-www-form-urlencoded")
res, err := client.Do(req)
if err != nil {
fmt.Println(err)
return
}
defer res.Body.Close()
_, err = ioutil.ReadAll(res.Body)
if err != nil {
fmt.Println(err)
return
}
if res.StatusCode != 200 {
fmt.Println(res.StatusCode)
}
}
My song-view model is this: (I'm just using it to sort the data, although the project is connected to the bank of the project in production, it is read-only)
type SongView struct {
Id int64 `json:"id"`
SongId int64 `json:"song_id" form:"song_id" gorm:"notNull" validate:"required,number"`
ArtistId int64 `json:"artist_id"`
Lang string `json:"lang" validate:"required,oneof=pt en es de fr"`
Date time.Time `json:"date" gorm:"column:created_at" validate:"required"`
Views int64 `json:"views"`
}
I believe that this code can be written a little more easily in Go, but that is not the question. From your description, it appears that the data is lost somewhere. Have you tried the Go data race detector tool? Below is a link
https://go.dev/doc/articles/race_detector
Can you provide examples of input data where errors/missing items appear?
It happens because your code has a race condition in between read and write to map.
Example:
G1 - goroutine 1
G2 - goroutine 2
G1: ReadLock and Read songview named "MySong"
G1: MySong doesn't exists. Nil will be inserted and returned.
G1: Unlock
G2: ReadLock and Read songview named "MySong"
G2: MySong exist Nil. Nil will be returned.
G1: WriteLock.
G1: songView = nil, so create a new one. Set counter to 1.
G1: set counter to 1. Insert to map on key "MySong"
G1: Unlock
G2: WriteLock: songView = nil(because you read it on step 2). Create new SongView. Set counter to 1.
G2: unlock
As a result you have 1 "MySong" with counter 1 because you rewrite a previous value.
The idea of locking - Atomicity. So, all your operation should be atomic.
func initSongView(name string, sview models.SongView) bool {
StoreControl.Lock()
defer StoreControl.Unlock()
songview := StoreViewsMap[name]
if songview == nil {
insert := models.SongView{
SongId: sview.SongId,
Lang: sview.Lang,
Date: sview.Date,
Views: 1, // counter should be 1 because it's a first view
}
StoreViewsMap[name] = &insert
} else {
songview.Views = songview.Views + 1
}
return true
}
I am building a daemon and I have two services that will be sending data to and from each other. Service A is what produces the data and service B a is Data Buffer service or like a queue. So from the main.go file, service B is instantiated and started. The Start() method will perform the buffer() function as a goroutine because this function waits for data to be passed onto a channel and I don't want the main process to halt waiting for buffer to complete. Then Service A is instantiated and started. It is then also "registered" with Service B.
I created a method called RegisterWithBufferService for Service A that creates two new channels. It will store those channels as it's own attributes and also provide them to Service B.
func (s *ServiceA) RegisterWithBufferService(bufService *data.DataBuffer) error {
newIncomingChan := make(chan *data.DataFrame, 1)
newOutgoingChan := make(chan []byte, 1)
s.IncomingBuffChan = newIncomingChan
s.OutgoingDataChannels = append(s.OutgoingDataChannels, newOutgoingChan)
bufService.DataProviders[s.ServiceName()] = data.DataProviderInfo{
IncomingChan: newOutgoingChan, //our outGoing channel is their incoming
OutgoingChan: newIncomingChan, // our incoming channel is their outgoing
}
s.DataBufferService = bufService
bufService.NewProvider <- s.ServiceName() //The DataBuffer service listens for new services and creates a new goroutine for buffering
s.Logger.Info().Msg("Registeration completed.")
return nil
}
Buffer essentially listens for incoming data from Service A, decodes it using Decode() and then adds it to a slice called buf. If the slice is greater in length than bufferPeriod then it will send the first item in the slice in the Outgoing channel back to Service A.
func (b* DataBuffer) buffer(bufferPeriod int) {
for {
select {
case newProvider := <- b.NewProvider:
b.wg.Add(1)
/*
newProvider is a string
DataProviders is a map the value it returns is a struct containing the Incoming and
Outgoing channels for this service
*/
p := b.DataProviders[newProvider]
go func(prov string, in chan []byte, out chan *DataFrame) {
defer b.wg.Done()
var buf []*DataFrame
for {
select {
case rawData := <-in:
tmp := Decode(rawData) //custom decoding function. Returns a *DataFrame
buf = append(buf, tmp)
if len(buf) < bufferPeriod {
b.Logger.Info().Msg("Sending decoded data out.")
out <- buf[0]
buf = buf[1:] //pop
}
case <- b.Quit:
return
}
}
}(newProvider, p.IncomingChan, p.OutgoingChan)
}
case <- b.Quit:
return
}
}
Now Service A has a method called record that will periodically push data to all the channels in it's OutgoingDataChannels attribute.
func (s *ServiceA) record() error {
...
if atomic.LoadInt32(&s.Listeners) != 0 {
s.Logger.Info().Msg("Sending raw data to data buffer")
for _, outChan := range s.OutgoingDataChannels {
outChan <- dataBytes // the receiver (Service B) is already listening and this doesn't hang
}
s.Logger.Info().Msg("Raw data sent and received") // The logger will output this so I know it's not hanging
}
}
The problem is that Service A seems to push the data successfully using record but Service B never goes into the case rawData := <-in: case in the buffer sub-goroutine. Is this because I have nested goroutines? Incase it's not clear, when Service B is started, it calls buffer but because it would hang otherwise, I made the call to buffer a goroutine. So then when Service A calls RegisterWithBufferService, the buffer goroutine creates a goroutine to listen for new data from Service B and push it back to Service A once the buffer is filled. I hope I explained it clearly.
EDIT 1
I've made a minimal, reproducible example.
package main
import (
"fmt"
"sync"
"sync/atomic"
"time"
)
var (
defaultBufferingPeriod int = 3
DefaultPollingInterval int64 = 10
)
type DataObject struct{
Data string
}
type DataProvider interface {
RegisterWithBufferService(*DataBuffer) error
ServiceName() string
}
type DataProviderInfo struct{
IncomingChan chan *DataObject
OutgoingChan chan *DataObject
}
type DataBuffer struct{
Running int32 //used atomically
DataProviders map[string]DataProviderInfo
Quit chan struct{}
NewProvider chan string
wg sync.WaitGroup
}
func NewDataBuffer() *DataBuffer{
var (
wg sync.WaitGroup
)
return &DataBuffer{
DataProviders: make(map[string]DataProviderInfo),
Quit: make(chan struct{}),
NewProvider: make(chan string),
wg: wg,
}
}
func (b *DataBuffer) Start() error {
if ok := atomic.CompareAndSwapInt32(&b.Running, 0, 1); !ok {
return fmt.Errorf("Could not start Data Buffer Service.")
}
go b.buffer(defaultBufferingPeriod)
return nil
}
func (b *DataBuffer) Stop() error {
if ok := atomic.CompareAndSwapInt32(&b.Running, 1, 0); !ok {
return fmt.Errorf("Could not stop Data Buffer Service.")
}
for _, p := range b.DataProviders {
close(p.IncomingChan)
close(p.OutgoingChan)
}
close(b.Quit)
b.wg.Wait()
return nil
}
// buffer creates goroutines for each incoming, outgoing data pair and decodes the incoming bytes into outgoing DataFrames
func (b *DataBuffer) buffer(bufferPeriod int) {
for {
select {
case newProvider := <- b.NewProvider:
fmt.Println("Received new Data provider.")
if _, ok := b.DataProviders[newProvider]; ok {
b.wg.Add(1)
p := b.DataProviders[newProvider]
go func(prov string, in chan *DataObject, out chan *DataObject) {
defer b.wg.Done()
var (
buf []*DataObject
)
fmt.Printf("Waiting for data from: %s\n", prov)
for {
select {
case inData := <-in:
fmt.Printf("Received data from: %s\n", prov)
buf = append(buf, inData)
if len(buf) > bufferPeriod {
fmt.Printf("Queue is filled, sending data back to %s\n", prov)
out <- buf[0]
fmt.Println("Data Sent")
buf = buf[1:] //pop
}
case <- b.Quit:
return
}
}
}(newProvider, p.IncomingChan, p.OutgoingChan)
}
case <- b.Quit:
return
}
}
}
type ServiceA struct{
Active int32 // atomic
Stopping int32 // atomic
Recording int32 // atomic
Listeners int32 // atomic
name string
QuitChan chan struct{}
IncomingBuffChan chan *DataObject
OutgoingBuffChans []chan *DataObject
DataBufferService *DataBuffer
}
// A compile time check to ensure ServiceA fully implements the DataProvider interface
var _ DataProvider = (*ServiceA)(nil)
func NewServiceA() (*ServiceA, error) {
var newSliceOutChans []chan *DataObject
return &ServiceA{
QuitChan: make(chan struct{}),
OutgoingBuffChans: newSliceOutChans,
name: "SERVICEA",
}, nil
}
// Start starts the service. Returns an error if any issues occur
func (s *ServiceA) Start() error {
atomic.StoreInt32(&s.Active, 1)
return nil
}
// Stop stops the service. Returns an error if any issues occur
func (s *ServiceA) Stop() error {
atomic.StoreInt32(&s.Stopping, 1)
close(s.QuitChan)
return nil
}
func (s *ServiceA) StartRecording(pol_int int64) error {
if ok := atomic.CompareAndSwapInt32(&s.Recording, 0, 1); !ok {
return fmt.Errorf("Could not start recording. Data recording already started")
}
ticker := time.NewTicker(time.Duration(pol_int) * time.Second)
go func() {
for {
select {
case <-ticker.C:
fmt.Println("Time to record...")
err := s.record()
if err != nil {
return
}
case <-s.QuitChan:
ticker.Stop()
return
}
}
}()
return nil
}
func (s *ServiceA) record() error {
current_time := time.Now()
ct := fmt.Sprintf("%02d-%02d-%d", current_time.Day(), current_time.Month(), current_time.Year())
dataObject := &DataObject{
Data: ct,
}
if atomic.LoadInt32(&s.Listeners) != 0 {
fmt.Println("Sending data to Data buffer...")
for _, outChan := range s.OutgoingBuffChans {
outChan <- dataObject // the receivers should already be listening
}
fmt.Println("Data sent.")
}
return nil
}
// RegisterWithBufferService satisfies the DataProvider interface. It provides the bufService with new incoming and outgoing channels along with a polling interval
func (s ServiceA) RegisterWithBufferService(bufService *DataBuffer) error {
if _, ok := bufService.DataProviders[s.ServiceName()]; ok {
return fmt.Errorf("%v data provider already registered with Data Buffer.", s.ServiceName())
}
newIncomingChan := make(chan *DataObject, 1)
newOutgoingChan := make(chan *DataObject, 1)
s.IncomingBuffChan = newIncomingChan
s.OutgoingBuffChans = append(s.OutgoingBuffChans, newOutgoingChan)
bufService.DataProviders[s.ServiceName()] = DataProviderInfo{
IncomingChan: newOutgoingChan, //our outGoing channel is their incoming
OutgoingChan: newIncomingChan, // our incoming channel is their outgoing
}
s.DataBufferService = bufService
bufService.NewProvider <- s.ServiceName() //The DataBuffer service listens for new services and creates a new goroutine for buffering
return nil
}
// ServiceName satisfies the DataProvider interface. It returns the name of the service.
func (s ServiceA) ServiceName() string {
return s.name
}
func main() {
var BufferedServices []DataProvider
fmt.Println("Instantiating and Starting Data Buffer Service...")
bufService := NewDataBuffer()
err := bufService.Start()
if err != nil {
panic(fmt.Sprintf("%v", err))
}
defer bufService.Stop()
fmt.Println("Data Buffer Service successfully started.")
fmt.Println("Instantiating and Starting Service A...")
serviceA, err := NewServiceA()
if err != nil {
panic(fmt.Sprintf("%v", err))
}
BufferedServices = append(BufferedServices, *serviceA)
err = serviceA.Start()
if err != nil {
panic(fmt.Sprintf("%v", err))
}
defer serviceA.Stop()
fmt.Println("Service A successfully started.")
fmt.Println("Registering services with Data Buffer...")
for _, s := range BufferedServices {
_ = s.RegisterWithBufferService(bufService) // ignoring error msgs for base case
}
fmt.Println("Registration complete.")
fmt.Println("Beginning recording...")
_ = atomic.AddInt32(&serviceA.Listeners, 1)
err = serviceA.StartRecording(DefaultPollingInterval)
if err != nil {
panic(fmt.Sprintf("%v", err))
}
for {
select {
case RTD := <-serviceA.IncomingBuffChan:
fmt.Println(RTD)
case <-serviceA.QuitChan:
atomic.StoreInt32(&serviceA.Listeners, 0)
bufService.Quit<-struct{}{}
}
}
}
Running on Go 1.17. When running the example, it should print the following every 10 seconds:
Time to record...
Sending data to Data buffer...
Data sent.
But then Data buffer never goes into the inData := <-in case.
To diagnose this I changed fmt.Println("Sending data to Data buffer...") to fmt.Println("Sending data to Data buffer...", s.OutgoingBuffChans) and the output was:
Time to record...
Sending data to Data buffer... []
So you are not actually sending the data to any channels. The reason for this is:
func (s ServiceA) RegisterWithBufferService(bufService *DataBuffer) error {
As the receiver is not a pointer when you do the s.OutgoingBuffChans = append(s.OutgoingBuffChans, newOutgoingChan) you are changing s.OutgoingBuffChans in a copy of the ServiceA which is discarded when the function exits. To fix this change:
func (s ServiceA) RegisterWithBufferService(bufService *DataBuffer) error {
to
func (s *ServiceA) RegisterWithBufferService(bufService *DataBuffer) error {
and
BufferedServices = append(BufferedServices, *serviceA)
to
BufferedServices = append(BufferedServices, serviceA)
The amended version outputs:
Time to record...
Sending data to Data buffer... [0xc0000d8060]
Data sent.
Received data from: SERVICEA
Time to record...
Sending data to Data buffer... [0xc0000d8060]
Data sent.
Received data from: SERVICEA
So this resolves the reported issue (I would not be suprised if there are other issues but hopefully this points you in the right direction). I did notice that the code you originally posted does use a pointer receiver so that might have suffered from another issue (but its difficult to comment on code fragments in a case like this).
I am trying to work with the Ethereum blockchain, with the Solidity contracts.
I am currently deploying a contract and performing some actions on it, but I would like to know how to get some 'feedback/callback/returns' of a specific transaction.
Is there a way to set the status of a transaction to 0 (error) and still get events, for example ?
if (id.length <= 0) {
emit Result("KO", "1");
revert();
}
This will not work (no event) because I revert everything, but the status will be set to 0
if (id.length <= 0) {
emit Result("KO", "1");
return;
}
I will get some event, but the status will stay 1
if (id.length <= 0) {
revert("KO_1");
}
The status will be 0, but I will not have any event
Here is my go code to perform the action :
func testFunction(id []byte) {
//...
//...
tx, err := instance.Action(opt, id)
if (errors.HasError(err)) {
return
}
callbackValue := subscribeToContract(tx.Hash().Hex())
logs.Pretty(tx, callbackValue)
//...
//...
}
func subscribeToContract(hashToRead string) myStruct {
query := ethereum.FilterQuery{
Addresses: []common.Address{address},
}
soc := make(chan types.Log)
sub, err := WssClient.SubscribeFilterLogs(context.Background(), query, soc)
if err != nil {
logs.Error(err)
}
for {
select {
case err := <-sub.Err():
logs.Info(`SOMETHING ERROR`)
logs.Error(err)
case vLog := <-soc:
logs.Info(`SOMETHING`)
contractAbi, _ := abi.JSON(strings.NewReader(string(SignABI)))
event := myStruct{}
contractAbi.Unpack(&event, "Result", vLog.Data)
logs.Info(`New Event from [` + vLog.TxHash.Hex() + `] : ` + event.Message)
}
}
}
If id.length > 0, all good.
But if id.length <= 0, I have no callback from the subscribeToContract function.
Is there a way to directly have the result status, or should loop with tx, err := client.TransactionReceipt(context.Background(), txHash) until I get a Status?
I didn't found any way to subscribe to a specific status change for a transaction, but a workaround :
The go-ethereum package provides 2 functions SubscribeFilterLogs and SubscribeNewHead. We can use the first one to get the logs (if relevant) and the second one to get a block information :
SubscribeNewHead subscribes to notifications about the current
blockchain head on the given channel.
A transaction can be validated or rejected/reverted when a block is mined, so we can use this 'trick'
func checkTransactionReceipt(_txHash string) int {
client, _ := getClient("https://ropsten.infura.io/v3/XXXXXX")
txHash := common.HexToHash(_txHash)
tx, err := client.TransactionReceipt(context.Background(), txHash)
if (Error.HasError(err)) {
return (-1)
}
return (int(tx.Status))
}
func WaitForBlockCompletation(data EthData, hashToRead string) int {
soc := make(chan *types.Header)
sub, err := data.WssClient.SubscribeNewHead(context.Background(), soc)
if (err != nil) {
return -1
}
for {
select {
case err := <-sub.Err():
_ = err
return -1
case header := <-soc:
logs.Info(header.TxHash.Hex())
transactionStatus := checkTransactionReceipt(hashToRead)
if (transactionStatus == 0) {
//FAILURE
sub.Unsubscribe()
return 0
} else if (transactionStatus == 1) {
//SUCCESS
sub.Unsubscribe()
return 1
}
}
}
}
Basically we are waiting for the block to be mined, then we check the TransactionReceipt which fail with an error (not found) if the transaction is not yet validated/rejected. Then, if the transaction is, we can unsubscribe the subscription and return the transaction status (0 fail, 1 success).
Not sure if it's the worst, best, only method, but it's working ! Be free to improve this solution !
An easier/newer solution:
I think the function waitMined is the function you are looking for.
bind.WaitMined(context.Background(), client, signedTx)
Originally posted in here.
I was wondering if someone can explain this syntax to me. In the google maps go api, they have
type Client struct {
httpClient *http.Client
apiKey string
baseURL string
clientID string
signature []byte
requestsPerSecond int
rateLimiter chan int
}
// NewClient constructs a new Client which can make requests to the Google Maps WebService APIs.
func NewClient(options ...ClientOption) (*Client, error) {
c := &Client{requestsPerSecond: defaultRequestsPerSecond}
WithHTTPClient(&http.Client{})(c) //???????????
for _, option := range options {
err := option(c)
if err != nil {
return nil, err
}
}
if c.apiKey == "" && (c.clientID == "" || len(c.signature) == 0) {
return nil, errors.New("maps: API Key or Maps for Work credentials missing")
}
// Implement a bursty rate limiter.
// Allow up to 1 second worth of requests to be made at once.
c.rateLimiter = make(chan int, c.requestsPerSecond)
// Prefill rateLimiter with 1 seconds worth of requests.
for i := 0; i < c.requestsPerSecond; i++ {
c.rateLimiter <- 1
}
go func() {
// Wait a second for pre-filled quota to drain
time.Sleep(time.Second)
// Then, refill rateLimiter continuously
for _ = range time.Tick(time.Second / time.Duration(c.requestsPerSecond)) {
c.rateLimiter <- 1
}
}()
return c, nil
}
// WithHTTPClient configures a Maps API client with a http.Client to make requests over.
func WithHTTPClient(c *http.Client) ClientOption {
return func(client *Client) error {
if _, ok := c.Transport.(*transport); !ok {
t := c.Transport
if t != nil {
c.Transport = &transport{Base: t}
} else {
c.Transport = &transport{Base: http.DefaultTransport}
}
}
client.httpClient = c
return nil
}
}
And this is the line I don't understand in NewClient
WithHTTPClient(&http.Client{})(c)
Why are there two ()()?
I see that WithHTTPClient takes in a *http.Client which that line does, but then it also passes in a pointer to the client struct declared above it?
WithHTTPClient returns a function, ie:
func WithHTTPClient(c *http.Client) ClientOption {
return func(client *Client) error {
....
return nil
}
}
WithHTTPClient(&http.Client{})(c) is just calling that function with c (a pointer to a Client) as parameter. It could be written as:
f := WithHTTPClient(&http.Client{})
f(c)
For example:
package package
// Dear user, CleanUp must only be used with defer: defer CleanUp()
func CleanUp() {
// some logic to check if call was deferred
// do tear down
}
And in userland code:
func main() {
package.CleanUp() // PANIC, CleanUp must be deferred!
}
But all should be fine if user runs:
func main() {
defer package.CleanUp() // good job, no panic
}
Things I already tried:
func DeferCleanUp() {
defer func() { /* do tear down */ }()
// But then I realized this was exactly the opposite of what I needed
// user doesn't need to call defer CleanUp anymore but...
}
// now if the APi is misused it can cause problems too:
defer DeferCleanUp() // a defer inception xD, question remains.
Alright, per OPs request and just for laughs, I'm posting this hacky approach to solving this by looking at the call stack and applying some heuristics.
DISCLAIMER: Do not use this in real code. I don't think checking deferred is even a good thing.
Also Note: this approach will only work if the executable and the source are on the same machine.
Link to gist: https://gist.github.com/dvirsky/dfdfd4066c70e8391dc5 (this doesn't work in the playground because you can't read the source file there)
package main
import(
"fmt"
"runtime"
"io/ioutil"
"bytes"
"strings"
)
func isDeferred() bool {
// Let's get the caller's name first
var caller string
if fn, _, _, ok := runtime.Caller(1); ok {
caller = function(fn)
} else {
panic("No caller")
}
// Let's peek 2 levels above this - the first level is this function,
// The second is CleanUp()
// The one we want is who called CleanUp()
if _, file, line, ok := runtime.Caller(2); ok {
// now we actually need to read the source file
// This should be cached of course to avoid terrible performance
// I copied this from runtime/debug, so it's a legitimate thing to do :)
data, err := ioutil.ReadFile(file)
if err != nil {
panic("Could not read file")
}
// now let's read the exact line of the caller
lines := bytes.Split(data, []byte{'\n'})
lineText := strings.TrimSpace(string(lines[line-1]))
fmt.Printf("Line text: '%s'\n", lineText)
// Now let's apply some ugly rules of thumb. This is the fragile part
// It can be improved with regex or actual AST parsing, but dude...
return lineText == "}" || // on simple defer this is what we get
!strings.Contains(lineText, caller) || // this handles the case of defer func() { CleanUp() }()
strings.Contains(lineText, "defer ")
} // not ok - means we were not clled from at least 3 levels deep
return false
}
func CleanUp() {
if !isDeferred() {
panic("Not Deferred!")
}
}
// This should not panic
func fine() {
defer CleanUp()
fmt.Println("Fine!")
}
// this should not panic as well
func alsoFine() {
defer func() { CleanUp() }()
fmt.Println("Also Fine!")
}
// this should panic
func notFine() {
CleanUp()
fmt.Println("Not Fine!")
}
// Taken from the std lib's runtime/debug:
// function returns, if possible, the name of the function containing the PC.
func function(pc uintptr) string {
fn := runtime.FuncForPC(pc)
if fn == nil {
return ""
}
name := fn.Name()
if lastslash := strings.LastIndex(name, "/"); lastslash >= 0 {
name = name[lastslash+1:]
}
if period := strings.Index(name, "."); period >= 0 {
name = name[period+1:]
}
name = strings.Replace(name, "·", ".", -1)
return name
}
func main(){
fine()
alsoFine()
notFine()
}