package runtime
import (
"internal/abi"
"internal/runtime/sys"
"unsafe"
)
const debugSelect = false
type scase struct {
c *hchan
elem unsafe .Pointer
}
var (
chansendpc = abi .FuncPCABIInternal (chansend )
chanrecvpc = abi .FuncPCABIInternal (chanrecv )
)
func selectsetpc (pc *uintptr ) {
*pc = sys .GetCallerPC ()
}
func sellock (scases []scase , lockorder []uint16 ) {
var c *hchan
for _ , o := range lockorder {
c0 := scases [o ].c
if c0 != c {
c = c0
lock (&c .lock )
}
}
}
func selunlock (scases []scase , lockorder []uint16 ) {
for i := len (lockorder ) - 1 ; i >= 0 ; i -- {
c := scases [lockorder [i ]].c
if i > 0 && c == scases [lockorder [i -1 ]].c {
continue
}
unlock (&c .lock )
}
}
func selparkcommit (gp *g , _ unsafe .Pointer ) bool {
gp .activeStackChans = true
gp .parkingOnChan .Store (false )
var lastc *hchan
for sg := gp .waiting ; sg != nil ; sg = sg .waitlink {
if sg .c .get () != lastc && lastc != nil {
unlock (&lastc .lock )
}
lastc = sg .c .get ()
}
if lastc != nil {
unlock (&lastc .lock )
}
return true
}
func block () {
gopark (nil , nil , waitReasonSelectNoCases , traceBlockForever , 1 )
}
func selectgo (cas0 *scase , order0 *uint16 , pc0 *uintptr , nsends , nrecvs int , block bool ) (int , bool ) {
gp := getg ()
if debugSelect {
print ("select: cas0=" , cas0 , "\n" )
}
cas1 := (*[1 << 16 ]scase )(unsafe .Pointer (cas0 ))
order1 := (*[1 << 17 ]uint16 )(unsafe .Pointer (order0 ))
ncases := nsends + nrecvs
scases := cas1 [:ncases :ncases ]
pollorder := order1 [:ncases :ncases ]
lockorder := order1 [ncases :][:ncases :ncases ]
var pcs []uintptr
if raceenabled && pc0 != nil {
pc1 := (*[1 << 16 ]uintptr )(unsafe .Pointer (pc0 ))
pcs = pc1 [:ncases :ncases ]
}
casePC := func (casi int ) uintptr {
if pcs == nil {
return 0
}
return pcs [casi ]
}
var t0 int64
if blockprofilerate > 0 {
t0 = cputicks ()
}
norder := 0
allSynctest := true
for i := range scases {
cas := &scases [i ]
if cas .c == nil {
cas .elem = nil
continue
}
if cas .c .bubble != nil {
if getg ().bubble != cas .c .bubble {
fatal ("select on synctest channel from outside bubble" )
}
} else {
allSynctest = false
}
if cas .c .timer != nil {
cas .c .timer .maybeRunChan (cas .c )
}
j := cheaprandn (uint32 (norder + 1 ))
pollorder [norder ] = pollorder [j ]
pollorder [j ] = uint16 (i )
norder ++
}
pollorder = pollorder [:norder ]
lockorder = lockorder [:norder ]
waitReason := waitReasonSelect
if gp .bubble != nil && allSynctest {
waitReason = waitReasonSynctestSelect
}
for i := range lockorder {
j := i
c := scases [pollorder [i ]].c
for j > 0 && scases [lockorder [(j -1 )/2 ]].c .sortkey () < c .sortkey () {
k := (j - 1 ) / 2
lockorder [j ] = lockorder [k ]
j = k
}
lockorder [j ] = pollorder [i ]
}
for i := len (lockorder ) - 1 ; i >= 0 ; i -- {
o := lockorder [i ]
c := scases [o ].c
lockorder [i ] = lockorder [0 ]
j := 0
for {
k := j *2 + 1
if k >= i {
break
}
if k +1 < i && scases [lockorder [k ]].c .sortkey () < scases [lockorder [k +1 ]].c .sortkey () {
k ++
}
if c .sortkey () < scases [lockorder [k ]].c .sortkey () {
lockorder [j ] = lockorder [k ]
j = k
continue
}
break
}
lockorder [j ] = o
}
if debugSelect {
for i := 0 ; i +1 < len (lockorder ); i ++ {
if scases [lockorder [i ]].c .sortkey () > scases [lockorder [i +1 ]].c .sortkey () {
print ("i=" , i , " x=" , lockorder [i ], " y=" , lockorder [i +1 ], "\n" )
throw ("select: broken sort" )
}
}
}
sellock (scases , lockorder )
var (
sg *sudog
c *hchan
k *scase
sglist *sudog
sgnext *sudog
qp unsafe .Pointer
nextp **sudog
)
var casi int
var cas *scase
var caseSuccess bool
var caseReleaseTime int64 = -1
var recvOK bool
for _ , casei := range pollorder {
casi = int (casei )
cas = &scases [casi ]
c = cas .c
if casi >= nsends {
sg = c .sendq .dequeue ()
if sg != nil {
goto recv
}
if c .qcount > 0 {
goto bufrecv
}
if c .closed != 0 {
goto rclose
}
} else {
if raceenabled {
racereadpc (c .raceaddr (), casePC (casi ), chansendpc )
}
if c .closed != 0 {
goto sclose
}
sg = c .recvq .dequeue ()
if sg != nil {
goto send
}
if c .qcount < c .dataqsiz {
goto bufsend
}
}
}
if !block {
selunlock (scases , lockorder )
casi = -1
goto retc
}
if gp .waiting != nil {
throw ("gp.waiting != nil" )
}
nextp = &gp .waiting
for _ , casei := range lockorder {
casi = int (casei )
cas = &scases [casi ]
c = cas .c
sg := acquireSudog ()
sg .g = gp
sg .isSelect = true
sg .elem .set (cas .elem )
sg .releasetime = 0
if t0 != 0 {
sg .releasetime = -1
}
sg .c .set (c )
*nextp = sg
nextp = &sg .waitlink
if casi < nsends {
c .sendq .enqueue (sg )
} else {
c .recvq .enqueue (sg )
}
if c .timer != nil {
blockTimerChan (c )
}
}
gp .param = nil
gp .parkingOnChan .Store (true )
gopark (selparkcommit , nil , waitReason , traceBlockSelect , 1 )
gp .activeStackChans = false
sellock (scases , lockorder )
gp .selectDone .Store (0 )
sg = (*sudog )(gp .param )
gp .param = nil
casi = -1
cas = nil
caseSuccess = false
sglist = gp .waiting
for sg1 := gp .waiting ; sg1 != nil ; sg1 = sg1 .waitlink {
sg1 .isSelect = false
sg1 .elem .set (nil )
sg1 .c .set (nil )
}
gp .waiting = nil
for _ , casei := range lockorder {
k = &scases [casei ]
if k .c .timer != nil {
unblockTimerChan (k .c )
}
if sg == sglist {
casi = int (casei )
cas = k
caseSuccess = sglist .success
if sglist .releasetime > 0 {
caseReleaseTime = sglist .releasetime
}
} else {
c = k .c
if int (casei ) < nsends {
c .sendq .dequeueSudoG (sglist )
} else {
c .recvq .dequeueSudoG (sglist )
}
}
sgnext = sglist .waitlink
sglist .waitlink = nil
releaseSudog (sglist )
sglist = sgnext
}
if cas == nil {
throw ("selectgo: bad wakeup" )
}
c = cas .c
if debugSelect {
print ("wait-return: cas0=" , cas0 , " c=" , c , " cas=" , cas , " send=" , casi < nsends , "\n" )
}
if casi < nsends {
if !caseSuccess {
goto sclose
}
} else {
recvOK = caseSuccess
}
if raceenabled {
if casi < nsends {
raceReadObjectPC (c .elemtype , cas .elem , casePC (casi ), chansendpc )
} else if cas .elem != nil {
raceWriteObjectPC (c .elemtype , cas .elem , casePC (casi ), chanrecvpc )
}
}
if msanenabled {
if casi < nsends {
msanread (cas .elem , c .elemtype .Size_ )
} else if cas .elem != nil {
msanwrite (cas .elem , c .elemtype .Size_ )
}
}
if asanenabled {
if casi < nsends {
asanread (cas .elem , c .elemtype .Size_ )
} else if cas .elem != nil {
asanwrite (cas .elem , c .elemtype .Size_ )
}
}
selunlock (scases , lockorder )
goto retc
bufrecv :
if raceenabled {
if cas .elem != nil {
raceWriteObjectPC (c .elemtype , cas .elem , casePC (casi ), chanrecvpc )
}
racenotify (c , c .recvx , nil )
}
if msanenabled && cas .elem != nil {
msanwrite (cas .elem , c .elemtype .Size_ )
}
if asanenabled && cas .elem != nil {
asanwrite (cas .elem , c .elemtype .Size_ )
}
recvOK = true
qp = chanbuf (c , c .recvx )
if cas .elem != nil {
typedmemmove (c .elemtype , cas .elem , qp )
}
typedmemclr (c .elemtype , qp )
c .recvx ++
if c .recvx == c .dataqsiz {
c .recvx = 0
}
c .qcount --
selunlock (scases , lockorder )
goto retc
bufsend :
if raceenabled {
racenotify (c , c .sendx , nil )
raceReadObjectPC (c .elemtype , cas .elem , casePC (casi ), chansendpc )
}
if msanenabled {
msanread (cas .elem , c .elemtype .Size_ )
}
if asanenabled {
asanread (cas .elem , c .elemtype .Size_ )
}
typedmemmove (c .elemtype , chanbuf (c , c .sendx ), cas .elem )
c .sendx ++
if c .sendx == c .dataqsiz {
c .sendx = 0
}
c .qcount ++
selunlock (scases , lockorder )
goto retc
recv :
recv (c , sg , cas .elem , func () { selunlock (scases , lockorder ) }, 2 )
if debugSelect {
print ("syncrecv: cas0=" , cas0 , " c=" , c , "\n" )
}
recvOK = true
goto retc
rclose :
selunlock (scases , lockorder )
recvOK = false
if cas .elem != nil {
typedmemclr (c .elemtype , cas .elem )
}
if raceenabled {
raceacquire (c .raceaddr ())
}
goto retc
send :
if raceenabled {
raceReadObjectPC (c .elemtype , cas .elem , casePC (casi ), chansendpc )
}
if msanenabled {
msanread (cas .elem , c .elemtype .Size_ )
}
if asanenabled {
asanread (cas .elem , c .elemtype .Size_ )
}
send (c , sg , cas .elem , func () { selunlock (scases , lockorder ) }, 2 )
if debugSelect {
print ("syncsend: cas0=" , cas0 , " c=" , c , "\n" )
}
goto retc
retc :
if caseReleaseTime > 0 {
blockevent (caseReleaseTime -t0 , 1 )
}
return casi , recvOK
sclose :
selunlock (scases , lockorder )
panic (plainError ("send on closed channel" ))
}
func (c *hchan ) sortkey () uintptr {
return uintptr (unsafe .Pointer (c ))
}
type runtimeSelect struct {
dir selectDir
typ unsafe .Pointer
ch *hchan
val unsafe .Pointer
}
type selectDir int
const (
_ selectDir = iota
selectSend
selectRecv
selectDefault
)
func reflect_rselect (cases []runtimeSelect ) (int , bool ) {
if len (cases ) == 0 {
block ()
}
sel := make ([]scase , len (cases ))
orig := make ([]int , len (cases ))
nsends , nrecvs := 0 , 0
dflt := -1
for i , rc := range cases {
var j int
switch rc .dir {
case selectDefault :
dflt = i
continue
case selectSend :
j = nsends
nsends ++
case selectRecv :
nrecvs ++
j = len (cases ) - nrecvs
}
sel [j ] = scase {c : rc .ch , elem : rc .val }
orig [j ] = i
}
if nsends +nrecvs == 0 {
return dflt , false
}
if nsends +nrecvs < len (cases ) {
copy (sel [nsends :], sel [len (cases )-nrecvs :])
copy (orig [nsends :], orig [len (cases )-nrecvs :])
}
order := make ([]uint16 , 2 *(nsends +nrecvs ))
var pc0 *uintptr
if raceenabled {
pcs := make ([]uintptr , nsends +nrecvs )
for i := range pcs {
selectsetpc (&pcs [i ])
}
pc0 = &pcs [0 ]
}
chosen , recvOK := selectgo (&sel [0 ], &order [0 ], pc0 , nsends , nrecvs , dflt == -1 )
if chosen < 0 {
chosen = dflt
} else {
chosen = orig [chosen ]
}
return chosen , recvOK
}
func (q *waitq ) dequeueSudoG (sgp *sudog ) {
x := sgp .prev
y := sgp .next
if x != nil {
if y != nil {
x .next = y
y .prev = x
sgp .next = nil
sgp .prev = nil
return
}
x .next = nil
q .last = x
sgp .prev = nil
return
}
if y != nil {
y .prev = nil
q .first = y
sgp .next = nil
return
}
if q .first == sgp {
q .first = nil
q .last = nil
}
}
The pages are generated with Golds v0.8.4 . (GOOS=linux GOARCH=amd64)
Golds is a Go 101 project developed by Tapir Liu .
PR and bug reports are welcome and can be submitted to the issue list .
Please follow @zigo_101 (reachable from the left QR code) to get the latest news of Golds .