mirror of
https://github.com/golang/go
synced 2024-11-23 21:20:03 -07:00
doc: add Defer, Panic, and Recover article
Originally published on The Go Programming Language Blog, August 4 2010. http://blog.golang.org/2010/08/defer-panic-and-recover.html Update #2547 R=golang-dev, r, r CC=golang-dev https://golang.org/cl/5479053
This commit is contained in:
parent
e505c9ccac
commit
49d82b4ca1
@ -8,7 +8,7 @@ TARG=tmpltohtml
|
||||
GOFILES=\
|
||||
tmpltohtml.go\
|
||||
|
||||
all: tmpltohtml go_tutorial.html effective_go.html go1.html
|
||||
all: tmpltohtml go_tutorial.html effective_go.html go1.html articles/defer_panic_recover.html
|
||||
|
||||
%.html: %.tmpl tmpltohtml
|
||||
./makehtml $*.tmpl
|
||||
|
274
doc/articles/defer_panic_recover.html
Normal file
274
doc/articles/defer_panic_recover.html
Normal file
@ -0,0 +1,274 @@
|
||||
<!-- Defer, Panic, and Recover -->
|
||||
|
||||
<p>
|
||||
Go has the usual mechanisms for control flow: if, for, switch, goto. It also
|
||||
has the go statement to run code in a separate goroutine. Here I'd like to
|
||||
discuss some of the less common ones: defer, panic, and recover.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
A <b>defer statement</b> pushes a function call onto a list. The list of saved
|
||||
calls is executed after the surrounding function returns. Defer is commonly
|
||||
used to simplify functions that perform various clean-up actions.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
For example, let's look at a function that opens two files and copies the
|
||||
contents of one file to the other:
|
||||
</p>
|
||||
|
||||
<pre><!--{{code "progs/defer.go" `/func CopyFile/` `/STOP/`}}
|
||||
-->func CopyFile(dstName, srcName string) (written int64, err error) {
|
||||
src, err := os.Open(srcName)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
dst, err := os.Create(dstName)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
written, err = io.Copy(dst, src)
|
||||
dst.Close()
|
||||
src.Close()
|
||||
return
|
||||
}
|
||||
</pre>
|
||||
|
||||
<p>
|
||||
This works, but there is a bug. If the second call to os.Open fails, the
|
||||
function will return without closing the source file. This can be easily
|
||||
remedied by putting a call to src.Close() before the second return statement,
|
||||
but if the function were more complex the problem might not be so easily
|
||||
noticed and resolved. By introducing defer statements we can ensure that the
|
||||
files are always closed:
|
||||
</p>
|
||||
|
||||
<pre><!--{{code "progs/defer2.go" `/func CopyFile/` `/STOP/`}}
|
||||
-->func CopyFile(dstName, srcName string) (written int64, err error) {
|
||||
src, err := os.Open(srcName)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
defer src.Close()
|
||||
|
||||
dst, err := os.Create(dstName)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
defer dst.Close()
|
||||
|
||||
return io.Copy(dst, src)
|
||||
}
|
||||
</pre>
|
||||
|
||||
<p>
|
||||
Defer statements allow us to think about closing each file right after opening
|
||||
it, guaranteeing that, regardless of the number of return statements in the
|
||||
function, the files <i>will</i> be closed.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
The behavior of defer statements is straightforward and predictable. There are
|
||||
three simple rules:
|
||||
</p>
|
||||
|
||||
<p>
|
||||
1. <i>A deferred function's arguments are evaluated when the defer statement is
|
||||
evaluated.</i>
|
||||
</p>
|
||||
|
||||
<p>
|
||||
In this example, the expression "i" is evaluated when the Println call is
|
||||
deferred. The deferred call will print "0" after the function returns.
|
||||
</p>
|
||||
|
||||
<pre><!--{{code "progs/defer.go" `/func a/` `/STOP/`}}
|
||||
-->func a() {
|
||||
i := 0
|
||||
defer fmt.Println(i)
|
||||
i++
|
||||
return
|
||||
}
|
||||
</pre>
|
||||
|
||||
<p>
|
||||
2. <i>Deferred function calls are executed in Last In First Out order
|
||||
</i>after<i> the surrounding function returns.</i>
|
||||
</p>
|
||||
|
||||
<p>
|
||||
This function prints "3210":
|
||||
</p>
|
||||
|
||||
<pre><!--{{code "progs/defer.go" `/func b/` `/STOP/`}}
|
||||
-->func b() {
|
||||
for i := 0; i < 4; i++ {
|
||||
defer fmt.Print(i)
|
||||
}
|
||||
}
|
||||
</pre>
|
||||
|
||||
<p>
|
||||
3. <i>Deferred functions may read and assign to the returning function's named
|
||||
return values.</i>
|
||||
</p>
|
||||
|
||||
<p>
|
||||
In this example, a deferred function increments the return value i <i>after</i>
|
||||
the surrounding function returns. Thus, this function returns 2:
|
||||
</p>
|
||||
|
||||
<pre><!--{{code "progs/defer.go" `/func c/` `/STOP/`}}
|
||||
-->func c() (i int) {
|
||||
defer func() { i++ }()
|
||||
return 1
|
||||
}
|
||||
</pre>
|
||||
|
||||
<p>
|
||||
This is convenient for modifying the error return value of a function; we will
|
||||
see an example of this shortly.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<b>Panic</b> is a built-in function that stops the ordinary flow of control and
|
||||
begins <i>panicking</i>. When the function F calls panic, execution of F stops,
|
||||
any deferred functions in F are executed normally, and then F returns to its
|
||||
caller. To the caller, F then behaves like a call to panic. The process
|
||||
continues up the stack until all functions in the current goroutine have
|
||||
returned, at which point the program crashes. Panics can be initiated by
|
||||
invoking panic directly. They can also be caused by runtime errors, such as
|
||||
out-of-bounds array accesses.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<b>Recover</b> is a built-in function that regains control of a panicking
|
||||
goroutine. Recover is only useful inside deferred functions. During normal
|
||||
execution, a call to recover will return nil and have no other effect. If the
|
||||
current goroutine is panicking, a call to recover will capture the value given
|
||||
to panic and resume normal execution.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
Here's an example program that demonstrates the mechanics of panic and defer:
|
||||
</p>
|
||||
|
||||
<pre><!--{{code "progs/defer2.go" `/package main/` `/STOP/`}}
|
||||
-->package main
|
||||
|
||||
import "fmt"
|
||||
|
||||
func main() {
|
||||
f()
|
||||
fmt.Println("Returned normally from f.")
|
||||
}
|
||||
|
||||
func f() {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
fmt.Println("Recovered in f", r)
|
||||
}
|
||||
}()
|
||||
fmt.Println("Calling g.")
|
||||
g(0)
|
||||
fmt.Println("Returned normally from g.")
|
||||
}
|
||||
|
||||
func g(i int) {
|
||||
if i > 3 {
|
||||
fmt.Println("Panicking!")
|
||||
panic(fmt.Sprintf("%v", i))
|
||||
}
|
||||
defer fmt.Println("Defer in g", i)
|
||||
fmt.Println("Printing in g", i)
|
||||
g(i + 1)
|
||||
}
|
||||
</pre>
|
||||
|
||||
<p>
|
||||
The function g takes the int i, and panics if i is greater than 3, or else it
|
||||
calls itself with the argument i+1. The function f defers a function that calls
|
||||
recover and prints the recovered value (if it is non-nil). Try to picture what
|
||||
the output of this program might be before reading on.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
The program will output:
|
||||
</p>
|
||||
|
||||
<pre>Calling g.
|
||||
Printing in g 0
|
||||
Printing in g 1
|
||||
Printing in g 2
|
||||
Printing in g 3
|
||||
Panicking!
|
||||
Defer in g 3
|
||||
Defer in g 2
|
||||
Defer in g 1
|
||||
Defer in g 0
|
||||
Recovered in f 4
|
||||
Returned normally from f.</pre>
|
||||
|
||||
<p>
|
||||
If we remove the deferred function from f the panic is not recovered and
|
||||
reaches the top of the goroutine's call stack, terminating the program. This
|
||||
modified program will output:
|
||||
</p>
|
||||
|
||||
<pre>Calling g.
|
||||
Printing in g 0
|
||||
Printing in g 1
|
||||
Printing in g 2
|
||||
Printing in g 3
|
||||
Panicking!
|
||||
Defer in g 3
|
||||
Defer in g 2
|
||||
Defer in g 1
|
||||
Defer in g 0
|
||||
panic: 4
|
||||
|
||||
panic PC=0x2a9cd8
|
||||
[stack trace omitted]</pre>
|
||||
|
||||
<p>
|
||||
For a real-world example of <b>panic</b> and <b>recover</b>, see the
|
||||
<a href="/pkg/encoding/json/">json package</a> from the Go standard library.
|
||||
It decodes JSON-encoded data with a set of recursive functions.
|
||||
When malformed JSON is encountered, the parser calls panic is to unwind the
|
||||
stack to the top-level function call, which recovers from the panic and returns
|
||||
an appropriate error value (see the 'error' and 'unmarshal' functions in
|
||||
<a href="/src/pkg/encoding/json/decode.go">decode.go</a>).
|
||||
</p>
|
||||
|
||||
<p>
|
||||
The convention in the Go libraries is that even when a package uses panic
|
||||
internally, its external API still presents explicit error return values.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
Other uses of <b>defer</b> (beyond the file.Close() example given earlier)
|
||||
include releasing a mutex:
|
||||
</p>
|
||||
|
||||
<pre>mu.Lock()
|
||||
defer mu.Unlock()</pre>
|
||||
|
||||
<p>
|
||||
printing a footer:
|
||||
</p>
|
||||
|
||||
<pre>printHeader()
|
||||
defer printFooter()</pre>
|
||||
|
||||
<p>
|
||||
and more.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
In summary, the defer statement (with or without panic and recover) provides an
|
||||
unusual and powerful mechanism for control flow. It can be used to model a
|
||||
number of features implemented by special-purpose structures in other
|
||||
programming languages. Try it out.
|
||||
</p>
|
193
doc/articles/defer_panic_recover.tmpl
Normal file
193
doc/articles/defer_panic_recover.tmpl
Normal file
@ -0,0 +1,193 @@
|
||||
<!-- Defer, Panic, and Recover -->
|
||||
|
||||
<p>
|
||||
Go has the usual mechanisms for control flow: if, for, switch, goto. It also
|
||||
has the go statement to run code in a separate goroutine. Here I'd like to
|
||||
discuss some of the less common ones: defer, panic, and recover.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
A <b>defer statement</b> pushes a function call onto a list. The list of saved
|
||||
calls is executed after the surrounding function returns. Defer is commonly
|
||||
used to simplify functions that perform various clean-up actions.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
For example, let's look at a function that opens two files and copies the
|
||||
contents of one file to the other:
|
||||
</p>
|
||||
|
||||
{{code "progs/defer.go" `/func CopyFile/` `/STOP/`}}
|
||||
|
||||
<p>
|
||||
This works, but there is a bug. If the second call to os.Open fails, the
|
||||
function will return without closing the source file. This can be easily
|
||||
remedied by putting a call to src.Close() before the second return statement,
|
||||
but if the function were more complex the problem might not be so easily
|
||||
noticed and resolved. By introducing defer statements we can ensure that the
|
||||
files are always closed:
|
||||
</p>
|
||||
|
||||
{{code "progs/defer2.go" `/func CopyFile/` `/STOP/`}}
|
||||
|
||||
<p>
|
||||
Defer statements allow us to think about closing each file right after opening
|
||||
it, guaranteeing that, regardless of the number of return statements in the
|
||||
function, the files <i>will</i> be closed.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
The behavior of defer statements is straightforward and predictable. There are
|
||||
three simple rules:
|
||||
</p>
|
||||
|
||||
<p>
|
||||
1. <i>A deferred function's arguments are evaluated when the defer statement is
|
||||
evaluated.</i>
|
||||
</p>
|
||||
|
||||
<p>
|
||||
In this example, the expression "i" is evaluated when the Println call is
|
||||
deferred. The deferred call will print "0" after the function returns.
|
||||
</p>
|
||||
|
||||
{{code "progs/defer.go" `/func a/` `/STOP/`}}
|
||||
|
||||
<p>
|
||||
2. <i>Deferred function calls are executed in Last In First Out order
|
||||
</i>after<i> the surrounding function returns.</i>
|
||||
</p>
|
||||
|
||||
<p>
|
||||
This function prints "3210":
|
||||
</p>
|
||||
|
||||
{{code "progs/defer.go" `/func b/` `/STOP/`}}
|
||||
|
||||
<p>
|
||||
3. <i>Deferred functions may read and assign to the returning function's named
|
||||
return values.</i>
|
||||
</p>
|
||||
|
||||
<p>
|
||||
In this example, a deferred function increments the return value i <i>after</i>
|
||||
the surrounding function returns. Thus, this function returns 2:
|
||||
</p>
|
||||
|
||||
{{code "progs/defer.go" `/func c/` `/STOP/`}}
|
||||
|
||||
<p>
|
||||
This is convenient for modifying the error return value of a function; we will
|
||||
see an example of this shortly.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<b>Panic</b> is a built-in function that stops the ordinary flow of control and
|
||||
begins <i>panicking</i>. When the function F calls panic, execution of F stops,
|
||||
any deferred functions in F are executed normally, and then F returns to its
|
||||
caller. To the caller, F then behaves like a call to panic. The process
|
||||
continues up the stack until all functions in the current goroutine have
|
||||
returned, at which point the program crashes. Panics can be initiated by
|
||||
invoking panic directly. They can also be caused by runtime errors, such as
|
||||
out-of-bounds array accesses.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<b>Recover</b> is a built-in function that regains control of a panicking
|
||||
goroutine. Recover is only useful inside deferred functions. During normal
|
||||
execution, a call to recover will return nil and have no other effect. If the
|
||||
current goroutine is panicking, a call to recover will capture the value given
|
||||
to panic and resume normal execution.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
Here's an example program that demonstrates the mechanics of panic and defer:
|
||||
</p>
|
||||
|
||||
{{code "progs/defer2.go" `/package main/` `/STOP/`}}
|
||||
|
||||
<p>
|
||||
The function g takes the int i, and panics if i is greater than 3, or else it
|
||||
calls itself with the argument i+1. The function f defers a function that calls
|
||||
recover and prints the recovered value (if it is non-nil). Try to picture what
|
||||
the output of this program might be before reading on.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
The program will output:
|
||||
</p>
|
||||
|
||||
<pre>Calling g.
|
||||
Printing in g 0
|
||||
Printing in g 1
|
||||
Printing in g 2
|
||||
Printing in g 3
|
||||
Panicking!
|
||||
Defer in g 3
|
||||
Defer in g 2
|
||||
Defer in g 1
|
||||
Defer in g 0
|
||||
Recovered in f 4
|
||||
Returned normally from f.</pre>
|
||||
|
||||
<p>
|
||||
If we remove the deferred function from f the panic is not recovered and
|
||||
reaches the top of the goroutine's call stack, terminating the program. This
|
||||
modified program will output:
|
||||
</p>
|
||||
|
||||
<pre>Calling g.
|
||||
Printing in g 0
|
||||
Printing in g 1
|
||||
Printing in g 2
|
||||
Printing in g 3
|
||||
Panicking!
|
||||
Defer in g 3
|
||||
Defer in g 2
|
||||
Defer in g 1
|
||||
Defer in g 0
|
||||
panic: 4
|
||||
|
||||
panic PC=0x2a9cd8
|
||||
[stack trace omitted]</pre>
|
||||
|
||||
<p>
|
||||
For a real-world example of <b>panic</b> and <b>recover</b>, see the
|
||||
<a href="/pkg/encoding/json/">json package</a> from the Go standard library.
|
||||
It decodes JSON-encoded data with a set of recursive functions.
|
||||
When malformed JSON is encountered, the parser calls panic is to unwind the
|
||||
stack to the top-level function call, which recovers from the panic and returns
|
||||
an appropriate error value (see the 'error' and 'unmarshal' functions in
|
||||
<a href="/src/pkg/encoding/json/decode.go">decode.go</a>).
|
||||
</p>
|
||||
|
||||
<p>
|
||||
The convention in the Go libraries is that even when a package uses panic
|
||||
internally, its external API still presents explicit error return values.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
Other uses of <b>defer</b> (beyond the file.Close() example given earlier)
|
||||
include releasing a mutex:
|
||||
</p>
|
||||
|
||||
<pre>mu.Lock()
|
||||
defer mu.Unlock()</pre>
|
||||
|
||||
<p>
|
||||
printing a footer:
|
||||
</p>
|
||||
|
||||
<pre>printHeader()
|
||||
defer printFooter()</pre>
|
||||
|
||||
<p>
|
||||
and more.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
In summary, the defer statement (with or without panic and recover) provides an
|
||||
unusual and powerful mechanism for control flow. It can be used to model a
|
||||
number of features implemented by special-purpose structures in other
|
||||
programming languages. Try it out.
|
||||
</p>
|
@ -5,8 +5,8 @@
|
||||
|
||||
set -e
|
||||
|
||||
TMPL=${1:-go_tutorial.tmpl} # input file
|
||||
HTML=$(basename $TMPL .tmpl).html # output file (basename)
|
||||
TMPL=${1:-go_tutorial.tmpl} # input file
|
||||
HTML=$(dirname $TMPL)/$(basename $TMPL .tmpl).html # output file
|
||||
|
||||
if ! test -w $HTML
|
||||
then
|
||||
|
53
doc/progs/defer.go
Normal file
53
doc/progs/defer.go
Normal file
@ -0,0 +1,53 @@
|
||||
// Copyright 2011 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// This file contains the code snippets included in "Defer, Panic, an Recover."
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
)
|
||||
|
||||
func a() {
|
||||
i := 0
|
||||
defer fmt.Println(i)
|
||||
i++
|
||||
return
|
||||
}
|
||||
// STOP OMIT
|
||||
|
||||
func b() {
|
||||
for i := 0; i < 4; i++ {
|
||||
defer fmt.Print(i)
|
||||
}
|
||||
}
|
||||
// STOP OMIT
|
||||
|
||||
func c() (i int) {
|
||||
defer func() { i++ }()
|
||||
return 1
|
||||
}
|
||||
// STOP OMIT
|
||||
|
||||
// Intial version.
|
||||
func CopyFile(dstName, srcName string) (written int64, err error) {
|
||||
src, err := os.Open(srcName)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
dst, err := os.Create(dstName)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
written, err = io.Copy(dst, src)
|
||||
dst.Close()
|
||||
src.Close()
|
||||
return
|
||||
}
|
||||
// STOP OMIT
|
56
doc/progs/defer2.go
Normal file
56
doc/progs/defer2.go
Normal file
@ -0,0 +1,56 @@
|
||||
// Copyright 2011 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// This file contains the code snippets included in "Defer, Panic, an Recover."
|
||||
|
||||
package main
|
||||
|
||||
import "fmt"
|
||||
import "io" // OMIT
|
||||
import "os" // OMIT
|
||||
|
||||
func main() {
|
||||
f()
|
||||
fmt.Println("Returned normally from f.")
|
||||
}
|
||||
|
||||
func f() {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
fmt.Println("Recovered in f", r)
|
||||
}
|
||||
}()
|
||||
fmt.Println("Calling g.")
|
||||
g(0)
|
||||
fmt.Println("Returned normally from g.")
|
||||
}
|
||||
|
||||
func g(i int) {
|
||||
if i > 3 {
|
||||
fmt.Println("Panicking!")
|
||||
panic(fmt.Sprintf("%v", i))
|
||||
}
|
||||
defer fmt.Println("Defer in g", i)
|
||||
fmt.Println("Printing in g", i)
|
||||
g(i + 1)
|
||||
}
|
||||
// STOP OMIT
|
||||
|
||||
// Revised version.
|
||||
func CopyFile(dstName, srcName string) (written int64, err error) {
|
||||
src, err := os.Open(srcName)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
defer src.Close()
|
||||
|
||||
dst, err := os.Create(dstName)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
defer dst.Close()
|
||||
|
||||
return io.Copy(dst, src)
|
||||
}
|
||||
// STOP OMIT
|
@ -20,25 +20,41 @@ else
|
||||
$GC file.go
|
||||
fi
|
||||
|
||||
defer_panic_recover="
|
||||
defer.go
|
||||
defer2.go
|
||||
"
|
||||
|
||||
effective_go="
|
||||
eff_bytesize.go
|
||||
eff_qr.go
|
||||
eff_sequence.go
|
||||
"
|
||||
|
||||
go_tutorial="
|
||||
cat.go
|
||||
cat_rot13.go
|
||||
echo.go
|
||||
file.go
|
||||
helloworld.go
|
||||
helloworld3.go
|
||||
print.go
|
||||
print_string.go
|
||||
server.go
|
||||
server1.go
|
||||
sieve.go
|
||||
sieve1.go
|
||||
sort.go
|
||||
sortmain.go
|
||||
strings.go
|
||||
sum.go
|
||||
"
|
||||
|
||||
for i in \
|
||||
helloworld.go \
|
||||
helloworld3.go \
|
||||
echo.go \
|
||||
cat.go \
|
||||
cat_rot13.go \
|
||||
sum.go \
|
||||
sort.go \
|
||||
sortmain.go \
|
||||
print.go \
|
||||
print_string.go \
|
||||
sieve.go \
|
||||
sieve1.go \
|
||||
server1.go \
|
||||
strings.go \
|
||||
eff_bytesize.go\
|
||||
eff_qr.go \
|
||||
eff_sequence.go\
|
||||
go1.go\
|
||||
$defer_panic_recover \
|
||||
$effective_go \
|
||||
$go_tutorial \
|
||||
go1.go \
|
||||
; do
|
||||
$GC $i
|
||||
done
|
||||
|
@ -31,6 +31,7 @@ import (
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strings"
|
||||
"text/template"
|
||||
@ -54,8 +55,8 @@ func main() {
|
||||
}
|
||||
|
||||
// Read and parse the input.
|
||||
name := flag.Args()[0]
|
||||
tmpl := template.New(name).Funcs(templateFuncs)
|
||||
name := flag.Arg(0)
|
||||
tmpl := template.New(filepath.Base(name)).Funcs(templateFuncs)
|
||||
if _, err := tmpl.ParseFiles(name); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user