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

Add pickle test #27

Merged
merged 16 commits into from
Nov 12, 2021
Merged

Add pickle test #27

merged 16 commits into from
Nov 12, 2021

Conversation

tlambert03
Copy link
Owner

this includes #26 ... and modifies the get/set state accordingly to deal with the new lock object.

@VolkerH ... after #26, have a look here

@VolkerH
Copy link
Contributor

VolkerH commented Nov 11, 2021 via email

@codecov
Copy link

codecov bot commented Nov 12, 2021

Codecov Report

Merging #27 (8cfda51) into main (1e2d45b) will decrease coverage by 0.06%.
The diff coverage is 93.93%.

❗ Current head 8cfda51 differs from pull request most recent head 25a0514. Consider uploading reports for the commit 25a0514 to get more accurate results
Impacted file tree graph

@@            Coverage Diff             @@
##             main      #27      +/-   ##
==========================================
- Coverage   87.29%   87.23%   -0.07%     
==========================================
  Files          11       11              
  Lines        1149     1159      +10     
==========================================
+ Hits         1003     1011       +8     
- Misses        146      148       +2     
Impacted Files Coverage Δ
src/nd2/_sdk/latest.pyx 84.21% <50.00%> (-0.34%) ⬇️
src/nd2/resource_backed_array.py 96.77% <92.30%> (ø)
src/nd2/nd2file.py 91.89% <100.00%> (+0.06%) ⬆️

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 1e2d45b...25a0514. Read the comment docs.

d = f.to_dask()
pd = pickle.dumps(d)
assert isinstance(pd, bytes)
# FIXME: this line currently leaks a file handle...
Copy link
Contributor

@VolkerH VolkerH Nov 12, 2021

Choose a reason for hiding this comment

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

Is this FIXME due to the __setstate__ method when unpickling?
__setstate__ will open the file, but I don't see the mechanism (context manager?) that would close the file again for an unpickled array. Maybe through garbage collection?

Copy link
Contributor

Choose a reason for hiding this comment

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

At least that was the case for my initial PR, but I guess the OpeningDaskArray might fix that as well.

Copy link
Contributor

@VolkerH VolkerH left a comment

Choose a reason for hiding this comment

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

Hi @tlambert03,

Thanks for this. I am awed.

I pulled this PR branch and it seems to work for my use cases, I think everything I mentioned in issues #19 and #25.
Moreover, I can now pip install locally and the shared library is found, so it seems to address #24 as well.

I left a couple of comments, nothing I feel strongly about. Would be great to have this merged and rolled out.
Thanks again!

d = f.to_dask()
pd = pickle.dumps(d)
assert isinstance(pd, bytes)
# FIXME: this line currently leaks a file handle...
Copy link
Contributor

Choose a reason for hiding this comment

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

At least that was the case for my initial PR, but I guess the OpeningDaskArray might fix that as well.

return method


class OpeningDaskArray(da.Array):
Copy link
Contributor

Choose a reason for hiding this comment

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

I like the approach with the subclass better than the ObjectProxy.
Not sure about the class name, sounds a bit like a morphological operation :-)
Throwing in ResourceHandlingDaskArray as a suggestion although that is quite a mouthfull.

@VolkerH
Copy link
Contributor

VolkerH commented Nov 12, 2021

Ooops ....
I was too quick.
While my tests pass, my actual application still crashes when I return the dask array from a context manager.
I will try and debug where that happens and provide a minimal example.

@VolkerH
Copy link
Contributor

VolkerH commented Nov 12, 2021

So what I see in the context of a larger application (shortened traceback) when returning the dask array from a ND2File context manager. When I leave ND2File open and return the array, I don't observe the crashes.

When I try and create a smaller example consisting of the following two steps

  • call function that reads nd2 file and returns dask_array from context manager
  • np.array(da_returned_from_context_manager[0,0,:,:])

it doesn't crash. Although these are exactly the steps that happen in the context of the larger application.

Fatal Python error: Segmentation fault

Thread 0x00007f26dba25700 (most recent call first):
  File "/home/hilsenst/miniconda3/envs/napari_latest/lib/python3.9/concurrent/futures/thread.py", line 75 in _worker
  File "/home/hilsenst/miniconda3/envs/napari_latest/lib/python3.9/threading.py", line 892 in run
  File "/home/hilsenst/miniconda3/envs/napari_latest/lib/python3.9/threading.py", line 954 in _bootstrap_inner
  File "/home/hilsenst/miniconda3/envs/napari_latest/lib/python3.9/threading.py", line 912 in _bootstrap
  File "/home/hilsenst/.vscode/extensions/ms-python.python-2021.11.1422169775/pythonFiles/lib/python/debugpy/_vendored/pydevd/_pydev_bundle/pydev_monkey.py", line 1054 in __call__

Thread 0x00007f275e7fc700 (most recent call first):

@VolkerH
Copy link
Contributor

VolkerH commented Nov 12, 2021

Now I can reproduce it with a small example

my test_nd2.py

import dask.array as da
import numpy as np
from nd2 import ND2File

# TODO: create a small test .nd2 on the Nikon that we can add to resources
# or add something to git lfs
dataset_nd2 = "/home/hilsenst/Documents/Luisa_Reference_HT/PreMaldi/Seq0000.nd2"

# removed a number of tests here for brevity
...

def _load_nd2(filepath):
    with ND2File(filepath) as f:
        return f.to_dask()


def test_nd2_from_context_mgr():
    arr = _load_nd2(dataset_nd2)
    np_slice = np.array(arr[0, 0, :, :])

Then runninng the test:

tests/test_nd2.py ....Fatal Python error: Segmentation fault

Thread 0x00007f4c0ecff700 (most recent call first):
  File "/home/hilsenst/miniconda3/envs/napari_latest/lib/python3.9/concurrent/futures/thread.py", line 75 in _worker
  File "/home/hilsenst/miniconda3/envs/napari_latest/lib/python3.9/threading.py", line 892 in run
  File "/home/hilsenst/miniconda3/envs/napari_latest/lib/python3.9/threading.py", line 954 in _bootstrap_inner
  File "/home/hilsenst/miniconda3/envs/napari_latest/lib/python3.9/threading.py", line 912 in _bootstrap

Current thread 0x00007f4c45f31180 (most recent call first):
  File "/home/hilsenst/GitlabEMBL/spacem-ht/src/spacem-mosaic/tests/test_nd2.py", line 53 in test_nd2_from_context_mgr
  File "/home/hilsenst/miniconda3/envs/napari_latest/lib/python3.9/site-packages/_pytest/python.py", line 183 in pytest_pyfunc_call
  File "/home/hilsenst/miniconda3/envs/napari_latest/lib/python3.9/site-packages/pluggy/callers.py", line 187 in _multicall
  File "/home/hilsenst/miniconda3/envs/napari_latest/lib/python3.9/site-packages/pluggy/manager.py", line 84 in <lambda>
  File "/home/hilsenst/miniconda3/envs/napari_latest/lib/python3.9/site-packages/pluggy/manager.py", line 93 in _hookexec
  File "/home/hilsenst/miniconda3/envs/napari_latest/lib/python3.9/site-packages/pluggy/hooks.py", line 286 in __call__
  File "/home/hilsenst/miniconda3/envs/napari_latest/lib/python3.9/site-packages/_pytest/python.py", line 1641 in runtest
  File "/home/hilsenst/miniconda3/envs/napari_latest/lib/python3.9/site-packages/_pytest/runner.py", line 162 in pytest_runtest_call
  File "/home/hilsenst/miniconda3/envs/napari_latest/lib/python3.9/site-packages/pluggy/callers.py", line 187 in _multicall
  File "/home/hilsenst/miniconda3/envs/napari_latest/lib/python3.9/site-packages/pluggy/manager.py", line 84 in <lambda>
  File "/home/hilsenst/miniconda3/envs/napari_latest/lib/python3.9/site-packages/pluggy/manager.py", line 93 in _hookexec
  File "/home/hilsenst/miniconda3/envs/napari_latest/lib/python3.9/site-packages/pluggy/hooks.py", line 286 in __call__
  File "/home/hilsenst/miniconda3/envs/napari_latest/lib/python3.9/site-packages/_pytest/runner.py", line 255 in <lambda>
  File "/home/hilsenst/miniconda3/envs/napari_latest/lib/python3.9/site-packages/_pytest/runner.py", line 311 in from_call
  File "/home/hilsenst/miniconda3/envs/napari_latest/lib/python3.9/site-packages/_pytest/runner.py", line 254 in call_runtest_hook
  File "/home/hilsenst/miniconda3/envs/napari_latest/lib/python3.9/site-packages/_pytest/runner.py", line 215 in call_and_report
  File "/home/hilsenst/miniconda3/envs/napari_latest/lib/python3.9/site-packages/_pytest/runner.py", line 126 in runtestprotocol
  File "/home/hilsenst/miniconda3/envs/napari_latest/lib/python3.9/site-packages/_pytest/runner.py", line 109 in pytest_runtest_protocol
  File "/home/hilsenst/miniconda3/envs/napari_latest/lib/python3.9/site-packages/pluggy/callers.py", line 187 in _multicall
  File "/home/hilsenst/miniconda3/envs/napari_latest/lib/python3.9/site-packages/pluggy/manager.py", line 84 in <lambda>
  File "/home/hilsenst/miniconda3/envs/napari_latest/lib/python3.9/site-packages/pluggy/manager.py", line 93 in _hookexec
  File "/home/hilsenst/miniconda3/envs/napari_latest/lib/python3.9/site-packages/pluggy/hooks.py", line 286 in __call__
  File "/home/hilsenst/miniconda3/envs/napari_latest/lib/python3.9/site-packages/_pytest/main.py", line 348 in pytest_runtestloop
  File "/home/hilsenst/miniconda3/envs/napari_latest/lib/python3.9/site-packages/pluggy/callers.py", line 187 in _multicall
  File "/home/hilsenst/miniconda3/envs/napari_latest/lib/python3.9/site-packages/pluggy/manager.py", line 84 in <lambda>
  File "/home/hilsenst/miniconda3/envs/napari_latest/lib/python3.9/site-packages/pluggy/manager.py", line 93 in _hookexec
  File "/home/hilsenst/miniconda3/envs/napari_latest/lib/python3.9/site-packages/pluggy/hooks.py", line 286 in __call__
  File "/home/hilsenst/miniconda3/envs/napari_latest/lib/python3.9/site-packages/_pytest/main.py", line 323 in _main
  File "/home/hilsenst/miniconda3/envs/napari_latest/lib/python3.9/site-packages/_pytest/main.py", line 269 in wrap_session
  File "/home/hilsenst/miniconda3/envs/napari_latest/lib/python3.9/site-packages/_pytest/main.py", line 316 in pytest_cmdline_main
  File "/home/hilsenst/miniconda3/envs/napari_latest/lib/python3.9/site-packages/pluggy/callers.py", line 187 in _multicall
  File "/home/hilsenst/miniconda3/envs/napari_latest/lib/python3.9/site-packages/pluggy/manager.py", line 84 in <lambda>
  File "/home/hilsenst/miniconda3/envs/napari_latest/lib/python3.9/site-packages/pluggy/manager.py", line 93 in _hookexec
  File "/home/hilsenst/miniconda3/envs/napari_latest/lib/python3.9/site-packages/pluggy/hooks.py", line 286 in __call__
  File "/home/hilsenst/miniconda3/envs/napari_latest/lib/python3.9/site-packages/_pytest/config/__init__.py", line 162 in main
  File "/home/hilsenst/miniconda3/envs/napari_latest/lib/python3.9/site-packages/_pytest/config/__init__.py", line 185 in console_main
  File "/home/hilsenst/miniconda3/envs/napari_latest/bin/pytest", line 8 in <module>
Segmentation fault (core dumped)

@tlambert03
Copy link
Owner Author

thank you @VolkerH! I really appreciate the help. I think we're narrowing in on something.

I will note that I found a few edge cases where I had to specify the scheduler to avoid a segfault (but I usually was able to figure out why), so let me try your example here.

not that this is a nice solution, but perhaps try the following?

def test_nd2_from_context_mgr():
    arr = _load_nd2(dataset_nd2)
    np_slice = np.array(arr[0, 0, :, :].compute(scheduler='threads'))

@tlambert03
Copy link
Owner Author

I'm inclined to merge what we have so far, and keep working at the edge cases... that ok with you?

@tlambert03
Copy link
Owner Author

tlambert03 commented Nov 12, 2021

Now I can reproduce it with a small example

my test_nd2.py

hmmm, this test actually passes fine for (on mac), both in pytest, as well as in ipython or as a script. you're on ubuntu yes?

@VolkerH
Copy link
Contributor

VolkerH commented Nov 12, 2021 via email

@VolkerH
Copy link
Contributor

VolkerH commented Nov 12, 2021

I'm inclined to merge what we have so far, and keep working at the edge cases... that ok with you?

Yes, I'm all for it.
I'm working with this version already and just work around the context manager issue by leaving the ND2File object in open state. Happy to leave this for a while, I think getting to the bottom of that threading/segfault problem might be time-consuming (and probably exceeds my skills)

@tlambert03
Copy link
Owner Author

got the leaked file figured out! One of the key details was that the dask array wasn't being "unpickled" back to a the ResourceBackedDaskArray ... (but rather a regular dask array). Overriding the __reduce__ method let me restore it as the ResourceBacked array, wherein I could call setstate and close the underlying file_ctx. learned a thing or two about pickle in the process!

@tlambert03 tlambert03 merged commit 7e335de into main Nov 12, 2021
@VolkerH
Copy link
Contributor

VolkerH commented Nov 12, 2021

Just saw this. Great that you could track it down.
Will test first thing Monday!

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