-
Notifications
You must be signed in to change notification settings - Fork 315
Lazy netcdf saves #5191
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
Merged
Merged
Lazy netcdf saves #5191
Changes from 15 commits
Commits
Show all changes
92 commits
Select commit
Hold shift + click to select a range
cd7fa42
Basic functional lazy saving.
pp-mo 1f32800
Simplify function signature which upsets Sphinx.
pp-mo e0f980f
Non-lazy saves return nothing.
pp-mo 67b96cf
Now fixed to enable use with process/distributed scheduling.
pp-mo 8cdbc9b
Remove dask.utils.SerializableLock, which I think was a mistake.
pp-mo 8723f24
Make DefferedSaveWrapper use _thread_safe_nc.
pp-mo d19a87f
Fixes for non-lazy save.
pp-mo 45e0e60
Avoid saver error when no deferred writes.
pp-mo 6a83200
Reorganise locking code, ready for shareable locks.
pp-mo 78a9346
Remove optional usage of 'filelock' for lazy saves.
pp-mo 31b12f7
Document dask-specific locking; implement differently for threads or …
pp-mo 99b4f41
Minor fix for unit-tests.
pp-mo 5d0a707
Pin libnetcdf to avoid problems -- see #5187.
pp-mo 431036f
Minor test fix.
pp-mo dc368d9
Move DeferredSaveWrapper into _thread_safe_nc; replicate the NetCDFDa…
pp-mo 6756a46
Update lib/iris/fileformats/netcdf/saver.py
pp-mo 80b4b6c
Update lib/iris/fileformats/netcdf/_dask_locks.py
pp-mo 78a8716
Update lib/iris/fileformats/netcdf/saver.py
pp-mo 47bb08b
Small rename + reformat.
pp-mo 0ece09a
Remove Saver lazy option; all lazy saves are delayed; factor out fill…
pp-mo 940f544
Merge branch 'main' into lazy_save_2
pp-mo eb97130
Merge remote-tracking branch 'upstream/main' into lazy_save_2
pp-mo 4596081
Repurposed 'test__FillValueMaskCheckAndStoreTarget' to 'test__data_fi…
pp-mo ad49fbe
Disable (temporary) saver debug printouts.
pp-mo b29c927
Fix test problems; Saver automatically completes to preserve existing…
pp-mo 8f10281
Fix docstring error.
pp-mo 6a564d9
Fix spurious error in old saver test.
pp-mo 2fb4d6c
Fix Saver docstring.
pp-mo c84bfdc
More robust exit for NetCDFWriteProxy operation.
pp-mo 5b78085
Fix doctests by making the Saver example functional.
pp-mo 478332e
Improve docstrings; unify terminology; simplify non-lazy save call.
pp-mo 34f154c
Moved netcdf cell-method handling into nc_load_rules.helpers, and var…
pp-mo d3744ba
Merge branch 'latest' into lazy_save_2
pp-mo 9673ea0
Fix lockfiles and Makefile process.
pp-mo bcbcbc8
Add unit tests for routine _fillvalue_report().
pp-mo 05c04a1
Remove debug-only code.
pp-mo 679ea47
Added tests for what the save function does with the 'compute' keyword.
pp-mo 70ec9dd
Fix mock-specific problems, small tidy.
pp-mo 28a4674
Restructure hierarchy of tests.unit.fileformats.netcdf
pp-mo 67f4b2b
Tidy test docstrings.
pp-mo ebec72f
Correct test import.
pp-mo 1f5b904
Avoid incorrect checking of byte data, and a numpy deprecation warning.
pp-mo 5045c9f
Alter parameter names to make test reports clearer.
pp-mo 393407a
Test basic behaviour of _lazy_stream_data; make 'Saver._delayed_write…
pp-mo 518360b
Add integration tests, and distributed dependency.
pp-mo 5c9931f
Docstring fixes.
pp-mo 7daee68
Documentation section and whatsnew entry.
pp-mo 97474f9
Merge branch 'main' into lazy_save_2
pp-mo 64c7251
Various fixes to whatsnew, docstrings and docs.
pp-mo 75043f9
Minor review changes, fix doctest.
pp-mo 445fbe2
Arrange tests + results to organise by package-name alone.
pp-mo 09cb22e
Review changes.
pp-mo 3445f58
Review changes.
pp-mo cb1e1f7
Enhance tests + debug.
pp-mo 1c81cee
Support scheduler type 'single-threaded'; allow retries on delayed-sa…
pp-mo 370837b
Improve test.
pp-mo 2f5f3c2
Adding a whatsnew entry for 5224 (#5234)
HGWright a55c6f2
Replacing numpy legacy printing with array2string and remaking result…
HGWright 4914e99
adding a whatsnew entry
HGWright bd642cd
configure codecov
HGWright bc5bdd1
remove results creation commit from blame
HGWright 301e59e
fixing whatsnew entry
HGWright 7b3044d
Bump scitools/workflows from 2023.04.1 to 2023.04.2 (#5236)
dependabot[bot] 02f2b66
Use real array for data of of small netCDF variables. (#5229)
pp-mo a7e0689
Handle derived coordinates correctly in `concatenate` (#5096)
schlunma c4e8bbb
clarity on whatsnew entry contributors (#5240)
bjlittle e6661b8
Modernize and simplify iris.analysis._Groupby (#5015)
bouweandela afbdbbd
Finalises Lazy Data documentation (#5137)
ESadek-MO b8bb753
Fixes to _discontiguity_in_bounds (attempt 2) (#4975)
stephenworsley 97cc149
update ci locks location (#5228)
bjlittle f14a321
Updated environment lockfiles (#5211)
scitools-ci[bot] f7a0b87
Increase retries.
pp-mo 69ddd9d
Change debug to show which elements failed.
pp-mo 8235d60
update cf standard units (#5244)
ESadek-MO 724c6d2
libnetcdf <4.9 pin (#5242)
trexfeathers 4f50dc7
Avoid possible same-file crossover between tests.
pp-mo 0da68cf
Ensure all-different testfiles; load all vars lazy.
pp-mo e8b7bfd
Revert changes to testing framework.
pp-mo ad48caf
Remove repeated line from requirements/py*.yml (?merge error), and re…
pp-mo 291b587
Revert some more debug changes.
pp-mo b2260ef
Merge branch 'latest' into lazy_save_2
pp-mo 33a7d86
Reorganise test for better code clarity.
pp-mo db6932d
Use public 'Dataset.isopen()' instead of '._isopen'.
pp-mo 631e001
Create output files in unique temporary directories.
pp-mo 2869f97
Tests for fileformats.netcdf._dask_locks.
pp-mo 419727b
Merge branch 'latest' into lazy_save_2
pp-mo 2f4458b
Fix attribution names.
pp-mo 88b7a2a
Merge branch 'latest' into lazy_save_2
pp-mo 98a20e7
Fixed new py311 lockfile.
pp-mo bbc1167
Fix typos spotted by codespell.
pp-mo ed38e43
Add distributed test dep for python 3.11
pp-mo 54ec0f8
Fix lockfile for python 3.11
pp-mo File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,131 @@ | ||
| # Copyright Iris contributors | ||
| # | ||
| # This file is part of Iris and is released under the LGPL license. | ||
| # See COPYING and COPYING.LESSER in the root of the repository for full | ||
| # licensing details. | ||
| """ | ||
| Module containing code to create locks enabling dask workers to co-operate. | ||
|
|
||
| This matter is complicated by needing different solutions for different dask scheduler | ||
| types, i.e. local 'threads' scheduler, local 'processes' or distributed. | ||
|
|
||
| In any case, a "iris.fileformats.netcdf.saver.Saver" object contains a netCDF4.Dataset | ||
|
lbdreyer marked this conversation as resolved.
Outdated
|
||
| targetting an output file, and creates a Saver.lock object to serialise write-accesses | ||
| to the file from dask tasks : All dask-task file writes go via a | ||
| "iris.fileformats.netcdf.saver.DeferredSaveWrapper" object, which also contains a link | ||
| to the Saver.lock, and uses it to prevent workers from fouling each other. | ||
| For each chunk written, the DeferredSaveWrapper acquires the common per-file lock; | ||
| opens a Dataset on the file; performs a write to the relevant variable; closes the | ||
| Dataset and then releases the lock. | ||
|
|
||
| For a threaded scheduler, the Saver.lock is a simple threading.Lock(). The workers | ||
| (threads) execute tasks which contain a DeferredSaveWrapper, as above. All of those | ||
| contain the common lock, and this is simply **the same object** for all workers, since | ||
| they share an address space. | ||
|
|
||
| For a distributed scheduler, the Saver.lock is a `distributed.Lock()` which is | ||
| identified with the output filepath. This is distributed to the workers by | ||
| serialising the task function arguments, which will include the DeferredSaveWrapper. | ||
| A worker behaves like a process, though it may execute on a remote machine. When a | ||
| distributed.Lock is deserialised to reconstruct the worker task, this creates an object | ||
| that communicates with the scheduler. These objects behave as a single common lock, | ||
| as they all have the same string 'identity', so the scheduler implements inter-process | ||
| communication so that the mutally exclude each other. | ||
|
|
||
| It is also *conceivable* that multiple processes could write to the same file in | ||
| parallel, if the operating system supports it. However, this also requires that the | ||
| libnetcdf C library is built with parallel access option, which is not common. | ||
| With the "ordinary" libnetcdf build, a process which attempts to rpen for writing a file | ||
|
pp-mo marked this conversation as resolved.
Outdated
|
||
| which is _already_ open for writing simply raises an access error. | ||
| In any case, Iris netcdf saver will not support this mode of operation, at present. | ||
|
|
||
| We don't currently support a local "processes" type scheduler. If we did, the | ||
| behaviour should be very similar to a distributed scheduler. It would need to use some | ||
| other serialisable shared-lock solution in place of 'distributed.Lock', which requires | ||
| a distributed scheduler to function. | ||
|
|
||
| """ | ||
| import threading | ||
|
|
||
| import dask.array | ||
| import dask.base | ||
| import dask.multiprocessing | ||
| import dask.threaded | ||
|
|
||
|
|
||
| def dask_scheduler_is_distributed(): | ||
| """Return whether a distributed.Client is active.""" | ||
| # NOTE: this replicates logic in `dask.base.get_scheduler` : if a distributed client | ||
| # has been created + is still active, then the default scheduler will always be | ||
| # "distributed". | ||
| is_distributed = False | ||
| # NOTE: must still work when 'distributed' is not available. | ||
| try: | ||
| import distributed | ||
|
|
||
| client = distributed.get_client() | ||
| is_distributed = client is not None | ||
| except (ImportError, ValueError): | ||
| pass | ||
| return is_distributed | ||
|
|
||
|
|
||
| def get_dask_array_scheduler_type(): | ||
| """ | ||
| Work out what type of scheduler an array.compute*() will use. | ||
|
|
||
| Returns one of 'distributed', 'threads' or 'processes'. | ||
| The return value is a valid argument for dask.config.set(scheduler=<type>). | ||
| This cannot distinguish between distributed local and remote clusters -- both of | ||
| those simply return 'distributed'. | ||
|
|
||
| NOTE: this takes account of how dask is *currently* configured. It will be wrong | ||
| if the config changes before the compute actually occurs. | ||
|
|
||
| """ | ||
| if dask_scheduler_is_distributed(): | ||
| result = "distributed" | ||
| else: | ||
| # Call 'get_scheduler', which respects the config settings, but pass an array | ||
| # so we default to the default scheduler for that type of object. | ||
| trial_dask_array = dask.array.zeros(1) | ||
| get_function = dask.base.get_scheduler(collections=[trial_dask_array]) | ||
| # Detect the ones which we recognise. | ||
| if get_function == dask.threaded.get: | ||
| result = "threads" | ||
| elif get_function == dask.multiprocessing.get: | ||
| result = "processes" | ||
| else: | ||
| msg = f"Dask default scheduler for arrays is unrecognised : {get_function}" | ||
| raise ValueError(msg) | ||
|
|
||
| return result | ||
|
|
||
|
|
||
| def get_worker_lock(identity: str): | ||
| """ | ||
| Return a mutex Lock which can be shared by multiple Dask workers. | ||
|
|
||
| The type of Lock generated depends on the dask scheduler type, which must therefore | ||
| be set up before this is called. | ||
|
|
||
| """ | ||
| scheduler_type = get_dask_array_scheduler_type() | ||
| if scheduler_type == "threads": | ||
| # N.B. the "identity" string is never used in this case, as the same actual | ||
| # lock object is used by all workers. | ||
| lock = threading.Lock() | ||
| elif scheduler_type == "distributed": | ||
| from dask.distributed import Lock as DistributedLock | ||
|
|
||
| lock = DistributedLock(identity) | ||
| else: | ||
| msg = ( | ||
| "The configured dask array scheduler type is " | ||
| f'"{scheduler_type}", ' | ||
| "which is not supported by the Iris netcdf saver." | ||
| ) | ||
| raise ValueError(msg) | ||
|
|
||
| # NOTE: not supporting 'processes' scheduler, for now. | ||
| return lock | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.