The following is an abstract definition of the struct interface used to store the struct in a map. Since reading and writing are more frequent, I add read and write lock.

// add progress listener.
func (upload *UploaderGateway) AddProgress(key string, v ProgressListener) {
	upload.mutex.Lock()
	defer upload.mutex.Unlock()
	upload.ProgressMap[key] = v
}

//get progress listener.
func (upload *UploaderGateway) GetProgress(key string) (v ProgressListener, err error) {
	upload.mutex.RLock()
	defer upload.mutex.RUnlock()
	progressListener, ok := upload.ProgressMap[key]
	if! ok {return nil, errors.New("Get ProgressListener Not Found")
	}
	listener := progressListener.GetFormat()
	return &listener, nil
}

//delete progress listener.
func (upload *UploaderGateway) DeleteProgress(key string) {
	upload.mutex.Lock()
	defer upload.mutex.Unlock()
	delete(upload.ProgressMap, key)
}
Copy the code

Structure definition


type ProgressListener interface {
	oss.ProgressListener
	SetFileSha1(string) ProgressListener
	SetFileSize(int64) ProgressListener
	SetConsumedBytes(int64) ProgressListener
	GetFormat() OssProgressListener
}

// Define the progress bar listener.
type OssProgressListener struct {
	FileSha1      string `json:"file_sha1"`      //file sha1
	FileSize      int64  `json:"file_size"`      //file size
	ConsumedBytes int64  `json:"consumed_bytes"` //consumed bytes
	mutex         *sync.RWMutex
}

// set consumed bytes.
func (listener *OssProgressListener) SetConsumedBytes(value int64) ProgressListener {
	listener.mutex.Lock()
	defer listener.mutex.Unlock()
	listener.ConsumedBytes = value
	return listener
}


// Define the progress change event handler.
func (listener *OssProgressListener) ProgressChanged(event *oss.ProgressEvent) {
	listener.SetConsumedBytes(event.ConsumedBytes)
	//pretty.Printf("event: %# v\n", event)
	//pretty.Printf("listener: %# v\n", listener)
	switch event.EventType {
	case oss.TransferStartedEvent:
		fmt.Printf("Transmission started, bytes used: %d, total bytes: %d.\n",
			event.ConsumedBytes, listener.FileSize)
	case oss.TransferDataEvent:
		//if event.ConsumedBytes == 0 || listener.FileSize == 0 {
		// FMT.Printf(" transfer data, consume bytes: %d, total bytes: %d, %d%%.\n",
		// event.ConsumedBytes, listener.FileSize, event.ConsumedBytes)
		//} else {
		// FMT.Printf(" transfer data, consume bytes: %d, total bytes: %d, %d%%.\n",
		// event.ConsumedBytes, listener.FileSize, event.ConsumedBytes*100/listener.FileSize)
		/ /}
	case oss.TransferCompletedEvent:
		fmt.Printf("\n Transmission completed, number of bytes used: %d, total bytes: %d.\n",
			event.ConsumedBytes, listener.FileSize)
	case oss.TransferFailedEvent:
		fmt.Printf("\n Transmission failed, number of bytes used: %d, total bytes: %d.\n",
			event.ConsumedBytes, listener.FileSize)
	default:}}Copy the code

The above ProgressChanged function is a callback function that is called in real time to update the ConsumedBytes value.

So here’s the problem. When I call GetProgress, I will change the implementation of the ProgressListener (the interface: pointer type, pointer passed) *OssProgressListener The structure information is returned, and since it is in API form, the framework will parse the structure into JSON and return it to the browser. When parsing json, the underlying layer will still read the value of the structure. Cause concurrent read/write problems.

solution

We first implement the abstraction of a structure and return the structure information with a lock, because we also use the lock mechanism when writing ConsumedBytes.

completeProgressdefine

//get progress listener.
func (upload *UploaderGateway) GetProgress(key string) (v *OssProgressListener, err error) {
	upload.mutex.RLock()
	defer upload.mutex.RUnlock()
	progressListener, ok := upload.ProgressMap[key]
	if! ok {return nil, errors.New("Get ProgressListener Not Found")
	}
	listener := progressListener.GetFormat()
	return &listener, nil
}

Copy the code

In this case, the progressListener receives a value pass, so external changes do not affect the progressListener

/**
* Author: JeffreyBool
* Date: 2019/5/25
* Time: 19:13
* Software: GoLand
*/

package oss

import (
	"github.com/aliyun/aliyun-oss-go-sdk/oss"
	"fmt"
	"sync"
	"encoding/json"
)

type ProgressListener interface {
	oss.ProgressListener
	SetFileSha1(string) ProgressListener
	SetFileSize(int64) ProgressListener
	SetConsumedBytes(int64) ProgressListener
	GetFormat() OssProgressListener
}

// Define the progress bar listener.
type OssProgressListener struct {
	FileSha1      string `json:"file_sha1"`      //file sha1
	FileSize      int64  `json:"file_size"`      //file size
	ConsumedBytes int64  `json:"consumed_bytes"` //consumed bytes
	mutex         *sync.RWMutex
}

// Initialize the progress bar listener
func NewOssProgressListener(a) ProgressListener {
	return &OssProgressListener{mutex: new(sync.RWMutex)}
}

// set file sha1.
func (listener *OssProgressListener) SetFileSha1(value string) ProgressListener {
	listener.mutex.Lock()
	defer listener.mutex.Unlock()
	listener.FileSha1 = value
	return listener
}

// set file size.
func (listener *OssProgressListener) SetFileSize(value int64) ProgressListener {
	listener.mutex.Lock()
	defer listener.mutex.Unlock()
	listener.FileSize = value
	return listener
}

// set consumed bytes.
func (listener *OssProgressListener) SetConsumedBytes(value int64) ProgressListener {
	listener.mutex.Lock()
	defer listener.mutex.Unlock()
	listener.ConsumedBytes = value
	return listener
}

// Get the data
// Only use the value copy mode to prevent the json serializer from reading the value again and writing it.
func (listener *OssProgressListener) GetFormat(a) OssProgressListener {
	listener.mutex.RLock()
	defer listener.mutex.RUnlock()
	//bytes, _ := listener.Marshal()
	return *listener
}

// Json serialization lock... Preventing data conflicts
func (listener *OssProgressListener) Marshal(a) ([]byte, error) {
	listener.mutex.RLock()
	defer listener.mutex.RUnlock()
	return json.Marshal(listener)
}

// Define the progress change event handler.
func (listener *OssProgressListener) ProgressChanged(event *oss.ProgressEvent) {
	listener.SetConsumedBytes(event.ConsumedBytes)
	//pretty.Printf("event: %# v\n", event)
	//pretty.Printf("listener: %# v\n", listener)
	switch event.EventType {
	case oss.TransferStartedEvent:
		fmt.Printf("Transmission started, bytes used: %d, total bytes: %d.\n",
			event.ConsumedBytes, listener.FileSize)
	case oss.TransferDataEvent:
		//if event.ConsumedBytes == 0 || listener.FileSize == 0 {
		// FMT.Printf(" transfer data, consume bytes: %d, total bytes: %d, %d%%.\n",
		// event.ConsumedBytes, listener.FileSize, event.ConsumedBytes)
		//} else {
		// FMT.Printf(" transfer data, consume bytes: %d, total bytes: %d, %d%%.\n",
		// event.ConsumedBytes, listener.FileSize, event.ConsumedBytes*100/listener.FileSize)
		/ /}
	case oss.TransferCompletedEvent:
		fmt.Printf("\n Transmission completed, number of bytes used: %d, total bytes: %d.\n",
			event.ConsumedBytes, listener.FileSize)
	case oss.TransferFailedEvent:
		fmt.Printf("\n Transmission failed, number of bytes used: %d, total bytes: %d.\n",
			event.ConsumedBytes, listener.FileSize)
	default:}}Copy the code

The GetFormat above is the information we expose. Be careful to lock when reading. Then we need to return the pass-through type of the structure, never a pointer type. The default pass-value type will return a copy of the data, so that the data received from the outside is not the same variable address. In this way, there will be no data conflicts when doing JSON parsing.

The data conflict

The figure above is the cause of the data conflict.

To view data conflict, run the -race command

go run -race main.go

Thank you

Thank you for taking the time to read it. If you think it’s ok, you can share this site with more people. Write not good spray ha, little brother level limited ~~ 🤣🤣🤣

The original link: www.zhanggaoyuan.com/article/18

[Bug caused by Golang pointer type]

This site is available under a creative Share agreement “CC BY-NC 4.0 international”. Please give your name and credit for any reprint or use.