@@ -8,17 +8,14 @@ PyObject* idOrNone(PyObject* o)
8
8
return o ? o : Py_BuildValue("");
9
9
}
10
10
11
- PyObject* fetchPythonError()
11
+ void fetchPythonError(PyObject* excInfo )
12
12
{
13
13
PyObject *type, *value, *traceback;
14
- PyObject* ret;
15
14
16
15
PyErr_Fetch(&type, &value, &traceback);
17
- ret = PyTuple_New(3);
18
- PyTuple_SetItem(ret, 0, idOrNone(type));
19
- PyTuple_SetItem(ret, 1, idOrNone(value));
20
- PyTuple_SetItem(ret, 2, idOrNone(traceback));
21
- return ret;
16
+ PyTuple_SetItem(excInfo, 0, idOrNone(type));
17
+ PyTuple_SetItem(excInfo, 1, idOrNone(value));
18
+ PyTuple_SetItem(excInfo, 2, idOrNone(traceback));
22
19
}
23
20
*/
24
21
import "C"
@@ -97,9 +94,29 @@ func pyObjectToPyTypeObject(p *C.PyObject) *C.PyTypeObject {
97
94
return (* C .PyTypeObject )(unsafe .Pointer (p ))
98
95
}
99
96
97
+ // getPyErr returns a Python's exception as an error.
98
+ // getPyErr normally returns a pyErr (see its godoc for details) and clears
99
+ // exception state of the python interpreter. If the exception is a MemoryError,
100
+ // getPyErr returns pyNoMemoryError (without stacktrace) and does not clears
101
+ // the exception state. The easiest way to extract stacktrace for MemoryError
102
+ // is calling PyErr_Print(). Note that PyErr_Print() prints to stderr.
100
103
func getPyErr () error {
101
- excInfo := Object {p : C .fetchPythonError ()}
104
+ if isPyNoMemoryError () {
105
+ // Fetching stacktrace requires some memory,
106
+ // so just return an error without stacktrace.
107
+ return errPyNoMemory
108
+ }
109
+
110
+ // TODO: consider to reserve excInfo
111
+ excInfo := Object {p : C .PyTuple_New (3 )}
112
+ if excInfo .p == nil {
113
+ if isPyNoMemoryError () {
114
+ return errPyNoMemory
115
+ }
116
+ return getPyErr ()
117
+ }
102
118
defer excInfo .decRef ()
119
+ C .fetchPythonError (excInfo .p )
103
120
104
121
formatted , err := tracebackFormatException (excInfo )
105
122
if err != nil {
@@ -134,14 +151,23 @@ func extractLineFromFormattedErrorMessage(formatted Object, n C.Py_ssize_t) stri
134
151
return C .GoString (C .PyString_AsString (line ))
135
152
}
136
153
154
+ // pyErr represents an exception of python.
137
155
type pyErr struct {
138
- mainMsg string
139
- syntaxErrMsg string
140
- stackTrace string
156
+ mainMsg string // "main error message" (one line)
157
+ syntaxErrMsg string // syntax error description for SyntaxError (zero or two lines)
158
+ stackTrace string // stacktrace (zero or multiple lines)
141
159
}
142
160
143
161
// Error returns an error message string for pyErr.
144
162
// This string contains multiple lines for stacktrace.
145
163
func (e * pyErr ) Error () string {
146
164
return e .mainMsg + "\n " + e .syntaxErrMsg + e .stackTrace
147
165
}
166
+
167
+ func isPyNoMemoryError () bool {
168
+ return C .PyErr_ExceptionMatches (C .PyExc_MemoryError ) != 0
169
+ }
170
+
171
+ // errPyNoMemory is an error value representing an allocation error on Python.
172
+ // This is not a pyErr because it is difficult to extract stacktrace from Python when the heap is exhaused.
173
+ var errPyNoMemory = errors .New ("python interpreter failed to allocate memory" )
0 commit comments