Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added example for go routines #21

Open
wants to merge 2 commits into
base: master
Choose a base branch
from

Conversation

jshiwam
Copy link

@jshiwam jshiwam commented Jul 29, 2022

This PR contains example for goroutines

Copy link
Collaborator

@christian-korneck christian-korneck left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jshiwam thanks for this. I've left some comments from my first read over the code (likely incomplete, but should get you an idea of the kinds of things that might need improvement?).

defer python3.Py_Finalize()

// Prints any python error if it was here
// no needs to call it after each single check of PyErr_Occurred()
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think that's safe. The docs say:

Call this function only when the error indicator is set. Otherwise it will cause a fatal error!

defer odds.DecRef()

even := fooModule.GetAttrString("print_even") // new reference, a call DecRef() is needed
if even == nil && python3.PyErr_Occurred() != nil {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure if a failed GetAttrString will cause a PyErr. At least the docs don't mention it. Maybe only check for nil return value (but not PyErr_Occured) ?
https://docs.python.org/3/c-api/object.html#c.PyObject_GetAttrString

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@christian-korneck failed GetAttrString will cause a PyErr. Here is how I Verified it. I added some prints to check on what happens when the GetAttrString fails as follows:

odds := fooModule.GetAttrString("print_odds") // new reference, a call DecRef() is needed
	if odds == nil && python3.PyErr_Occurred() != nil {
		err = errors.New("error getting the attribute print_odds")
		fmt.Println(python3.PyUnicode_AsUTF8(python3.PyErr_Occurred().Repr()), odds)
		python3.PyErr_Print()
		return
	}
	defer odds.DecRef()

Here is the output:

<class 'AttributeError'> <nil>
AttributeError: module 'foo' has no attribute 'print_odds'
2022/08/16 10:29:00 error getting the attribute print_odds

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've asked in Python IRC what the cannonical way to do error handling is and was pointed to this:

"You normally don’t need to call PyErr_Occurred() to see whether an error occurred in a function call, since you should be able to tell from the return value."
https://docs.python.org/3/extending/extending.html#intermezzo-errors-and-exceptions

Looking at large open source projects it seems like people are using a lot of different variants. But just checking for NULL seems to be sufficient?

(I still need to check a few things, but probably will update my examples).


// Cleans the Go variable, because now a new owner is caring about related PyObject
// no action, such as a call DecRef(), is needed here
limit = nil
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this cleanup is too early? In the next block you're still using limit (to decref in case PyTuple_SetItem has failed).

}

args := python3.PyTuple_New(1) // new reference, a call DecRef() is needed
if args == nil && python3.PyErr_Occurred() != nil {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

again the docs aren't clear if this causes a PyErr. Only check for nil?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Even I am not sure on this one, for GetAttrString I was able to check if PyErr_Occured() gets triggered or not, but not sure how to check that here, for now will just check for nil.

}
defer even.DecRef()

limit := python3.PyLong_FromGoInt(50) // new reference, will stolen later, a call DecRef() is NOT needed
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

again the docs aren't clear if this causes a PyErr. Only check for nil?


// At the end of all, if there was an error
// prints the error and exit with the non-zero code
defer func() {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

do we need to do this error check with a defer? Wouldn't it maybe simpler and more idiomatic to wrap most part of main() in a new function that returns an error? And then call it from my main and check for an error there?

}

var wg sync.WaitGroup
wg.Add(2)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

would move this down right before the Goroutines start, for better readability, etc.

// so that goroutines can acquire it
state := python3.PyEval_SaveThread() // release the GIL, the GIL is unlocked for using by goroutines

go func() {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

would it be better to pass args as argument to the anonymous func? not sure

go func() {
runtime.LockOSThread()
_gstate := python3.PyGILState_Ensure() // acquire the GIL, the GIL is locked by the 2nd goroutine
even.Call(args, python3.PyDict_New())
Copy link
Collaborator

@christian-korneck christian-korneck Aug 15, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this call will return a PyObject (of type None, new reference), that you need to decref.
https://stackoverflow.com/a/8451069
https://docs.python.org/3/c-api/none.html#c.Py_None

@jshiwam
Copy link
Author

jshiwam commented Aug 31, 2022

@christian-korneck let me know if this PR needs some other changes

@christian-korneck
Copy link
Collaborator

sorry for the delay, I'm currently slow to respond. I'll have a look soon.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants