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

Set max_cores and max_workers to None #478

Merged
merged 7 commits into from
Nov 6, 2024
Merged

Set max_cores and max_workers to None #478

merged 7 commits into from
Nov 6, 2024

Conversation

jan-janssen
Copy link
Member

@jan-janssen jan-janssen commented Nov 6, 2024

Summary by CodeRabbit

  • New Features

    • Enhanced flexibility in the Executor class, allowing optional parameters for max_workers, max_cores, and other configuration options.
    • Improved task submission and dependency management in the ExecutorWithDependencies class, including visual representation of task dependencies upon exiting the context manager.
    • Added a new validation function for core validation and adjusted existing tests to accommodate optional parameters for max_workers, enhancing clarity in executor behavior during tests.
  • Bug Fixes

    • Adjusted validation logic in various functions to accommodate optional parameter types, improving error handling and control flow.
  • Documentation

    • Updated documentation for functions to reflect changes in parameter types and their flexibility.

Copy link
Contributor

coderabbitai bot commented Nov 6, 2024

Walkthrough

The changes in this pull request primarily focus on modifying the Executor class and related functions to enhance flexibility in parameter handling. Several parameters, including max_workers, max_cores, and others, have been changed from mandatory to optional, allowing them to default to None. The logic within the __new__ method of the Executor class has been clarified, particularly regarding the handling of the resource_dict. Additionally, various validation functions have been updated to accept optional parameters, improving their error handling and control flow.

Changes

File Path Change Summary
executorlib/__init__.py Updated Executor class constructor and __new__ method to make max_workers, max_cores, and other parameters optional (default to None).
executorlib/interactive/executor.py Modified create_executor function to accept Optional[int] for max_workers and max_cores. Updated ExecutorWithDependencies class's submit and __exit__ methods to improve task submission and dependency management.
executorlib/standalone/inputcheck.py Changed validation functions to accept Optional types for parameters, adjusting error handling and control flow accordingly.
tests/test_integration_pyiron_workflow.py Updated test methods to instantiate Executor with max_workers=1, refining concurrency control in tests.
tests/test_local_executor_future.py Modified test_independence_from_executor method to instantiate InteractiveExecutor with max_workers=1, ensuring single-threaded execution during tests.
tests/test_shell_interactive.py Enhanced test_shell_interactive_executor method by adding max_workers=1 to Executor instantiation, limiting concurrency to a single worker thread.
tests/test_shared_input_check.py Added validate_number_of_cores function and updated tests for check_max_workers_and_cores to accept None for max_workers and max_cores.

Possibly related PRs

  • Remove conda support in executorlib #418: The main PR modifies the Executor class to remove conda support, which is directly related to the changes in the executorlib/__init__.py file where conda parameters were removed.
  • Automatically set hostname_localhost #433: This PR modifies the Executor class constructor, changing the hostname_localhost parameter to be optional, which aligns with the changes made in the main PR regarding constructor flexibility.
  • Delete SubprocessExecutor and ShellExecutor #440: The removal of SubprocessExecutor and ShellExecutor indicates a consolidation of executor functionality, which relates to the main PR's focus on simplifying the Executor class.
  • Use resource dict for internal communication #455: This PR introduces a resource_dict parameter to the Executor, which is a significant change that complements the main PR's modifications to the constructor and method signatures.
  • Implement resource_dict for file executor #456: The introduction of resource_dict in the FileExecutor and changes to parameter handling are relevant as they reflect the ongoing effort to streamline executor configurations, similar to the main PR's objectives.
  • Add option to disable_dependencies for cache #477: The addition of the disable_dependencies parameter in the Executor class enhances its configurability, which is consistent with the main PR's goal of improving the Executor class's flexibility.

🐰 In a world where options bloom,
The Executor finds more room.
With parameters now optional and bright,
Flexibility shines, a delightful sight!
So hop along, let tasks take flight,
In the realm of code, all feels just right! 🌼


Thank you for using CodeRabbit. We offer it for free to the OSS community and would appreciate your support in helping us grow. If you find it useful, would you consider giving us a shout-out on your favorite social media?

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Generate unit testing code for this file.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit testing code for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

@jan-janssen jan-janssen linked an issue Nov 6, 2024 that may be closed by this pull request
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🧹 Outside diff range and nitpick comments (3)
executorlib/interactive/executor.py (1)

150-152: Consider documenting the default behavior.

The function documentation should be updated to explain what happens when both max_workers and max_cores are None. This is important for users to understand the resource allocation behavior in such cases.

executorlib/__init__.py (2)

107-110: Update method documentation to reflect optional parameters.

The docstring for __new__ still describes max_workers and max_cores as (int) without indicating they're optional. Please update the parameter descriptions to:

  • Indicate they're optional
  • Document the behavior when None is provided
-            max_workers (int): for backwards compatibility with the standard library...
+            max_workers (Optional[int]): for backwards compatibility with the standard library...
-            max_cores (int): defines the number cores which can be used in parallel
+            max_cores (Optional[int]): defines the number cores which can be used in parallel. If None...

Line range hint 177-183: Verify interaction between resource defaults and optional parameters.

The default resource_dict sets cores=1, but max_cores and max_workers can now be None. Please clarify:

  1. The precedence between these parameters
  2. How None values interact with the default resource configuration

Consider documenting these interactions in the class docstring to help users understand the behavior when different combinations of parameters are provided.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between d85a615 and 9739df8.

📒 Files selected for processing (3)
  • executorlib/__init__.py (2 hunks)
  • executorlib/interactive/executor.py (1 hunks)
  • executorlib/standalone/inputcheck.py (2 hunks)
🔇 Additional comments (2)
executorlib/interactive/executor.py (1)

150-152: Review resource allocation logic.

Making both parameters optional could impact how resources are allocated, especially in block allocation mode where max_cores is used to calculate max_workers (e.g., int(max_cores / cores_per_worker)). Ensure there's proper fallback behavior when these values are None.

#!/bin/bash
# Search for places where max_cores is used in calculations
rg "max_cores.*cores_per_worker" -A 2
executorlib/__init__.py (1)

86-89: Verify backward compatibility with existing code.

The change from required integers to Optional[int] with None defaults for max_workers and max_cores could impact existing code that expects these parameters to be integers. Please ensure that:

  1. Downstream code handles None values appropriately
  2. Default behavior when None is provided is well-defined
✅ Verification successful

Making parameters optional is safe and properly handled

The validation logic in validate_number_of_cores properly handles None values for both max_workers and max_cores:

  • Returns 1 if both are None (default case)
  • Uses max_workers if max_cores is None
  • Uses max_cores if provided
  • check_max_workers_and_cores validates these parameters for specific backends

The change to make these parameters optional is safe and well-implemented with appropriate fallback behavior.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Search for direct usage of max_workers/max_cores to verify handling of None values
rg -A 3 "max_(workers|cores)\s*[=:]" --type python

Length of output: 83


Script:

#!/bin/bash
# Search for direct usage of max_workers/max_cores without file type flag
rg -A 3 "max_(workers|cores)\s*[=:]"

Length of output: 31989


Script:

#!/bin/bash
# Search for validation and handling of None values
rg "def validate_number_of_cores|def check_max_workers_and_cores" -A 10

Length of output: 1599

Comment on lines 175 to 180
if max_workers is None and max_cores is None:
return 1
elif max_workers is not None and max_cores is None:
return max_workers
return max_cores
else:
return max_cores
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Critical: Function needs type safety improvements and better documentation.

Several issues need to be addressed:

  1. The function signature doesn't match the implementation (should use Optional[int])
  2. Missing null checks before using the parameters
  3. Docstring doesn't describe the new behavior with None values

Here's the suggested fix:

- def validate_number_of_cores(max_cores: int, max_workers: int) -> int:
+ def validate_number_of_cores(max_cores: Optional[int], max_workers: Optional[int]) -> int:
     """
     Validate the number of cores and return the appropriate value.
+
+    Args:
+        max_cores: Optional maximum number of cores
+        max_workers: Optional maximum number of workers
+
+    Returns:
+        int: 1 if both parameters are None,
+             max_workers if max_cores is None,
+             max_cores otherwise
     """
     if max_workers is None and max_cores is None:
         return 1
     elif max_workers is not None and max_cores is None:
         return max_workers
-    else:
+    elif max_cores is not None:
         return max_cores
+    else:
+        raise ValueError("Invalid combination of max_cores and max_workers")
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if max_workers is None and max_cores is None:
return 1
elif max_workers is not None and max_cores is None:
return max_workers
return max_cores
else:
return max_cores
def validate_number_of_cores(max_cores: Optional[int], max_workers: Optional[int]) -> int:
"""
Validate the number of cores and return the appropriate value.
Args:
max_cores: Optional maximum number of cores
max_workers: Optional maximum number of workers
Returns:
int: 1 if both parameters are None,
max_workers if max_cores is None,
max_cores otherwise
"""
if max_workers is None and max_cores is None:
return 1
elif max_workers is not None and max_cores is None:
return max_workers
elif max_cores is not None:
return max_cores
else:
raise ValueError("Invalid combination of max_cores and max_workers")

Comment on lines +134 to +141
def check_max_workers_and_cores(
max_workers: Optional[int], max_cores: Optional[int]
) -> None:
if max_workers is not None:
raise ValueError(
"The number of workers cannot be controlled with the pysqa based backend."
)
if max_cores != 1:
if max_cores is not None:
Copy link
Contributor

Choose a reason for hiding this comment

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

💡 Codebase verification

Review needs to be updated - test cases don't align with new optional parameters

The verification revealed an inconsistency between the changes and the test cases:

  • The function check_max_workers_and_cores was modified to make parameters optional (Optional[int])
  • However, the test cases in test_check_max_workers_and_cores still pass non-None values and expect ValueError
  • The actual usage in create_file_executor passes non-None values by default (max_workers=1, max_cores=1)

This indicates that either:

  1. The function shouldn't have been made optional, or
  2. The test cases and usage need to be updated to reflect the new optional behavior
🔗 Analysis chain

LGTM! Type hints and validation logic updated correctly.

The changes properly implement optional parameters while maintaining the original validation purpose. The error messages remain clear and appropriate.

Let's verify the usage of this function across the codebase:

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Search for all calls to check_max_workers_and_cores to ensure they handle None correctly
ast-grep --pattern 'check_max_workers_and_cores($$$)'

Length of output: 479


Script:

#!/bin/bash
# Let's check the test file to understand the test cases
cat tests/test_shared_input_check.py

# Also check the executor implementation to see how it handles these parameters
cat executorlib/cache/executor.py

Length of output: 8418

Comment on lines +150 to +152
max_workers: Optional[int] = None,
backend: str = "local",
max_cores: int = 1,
max_cores: Optional[int] = None,
Copy link
Contributor

Choose a reason for hiding this comment

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

💡 Codebase verification

Type mismatch in validate_number_of_cores function signature

The function signature in executorlib/standalone/inputcheck.py doesn't match the optional types:

def validate_number_of_cores(max_cores: int, max_workers: int) -> int:

While the implementation correctly handles None values, the type hints need to be updated to Optional[int] to match the caller's signature.

  • executorlib/standalone/inputcheck.py: Update parameter types to Optional[int]
🔗 Analysis chain

Verify validation handling for None values.

While making max_workers and max_cores optional aligns with the PR objectives, we should ensure that validate_number_of_cores properly handles None values for both parameters.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Search for validate_number_of_cores implementation to verify None handling
ast-grep --pattern 'def validate_number_of_cores($_: Optional[int], $_: Optional[int])'

Length of output: 1023


Script:

#!/bin/bash
# Let's search for validate_number_of_cores implementation with a broader pattern
rg "def validate_number_of_cores" -A 10

Length of output: 750

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Outside diff range and nitpick comments (1)
tests/test_local_executor_future.py (1)

71-73: LGTM! Consider documenting the max_workers=1 choice.

The addition of max_workers=1 improves test determinism by ensuring single-threaded execution. This is particularly important for this test which verifies future independence from executor lifecycle.

Consider adding a comment explaining why max_workers=1 is specifically chosen here to help future maintainers understand the test's constraints.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between 3e824b0 and 10ffbf3.

📒 Files selected for processing (3)
  • tests/test_integration_pyiron_workflow.py (6 hunks)
  • tests/test_local_executor_future.py (2 hunks)
  • tests/test_shell_interactive.py (1 hunks)
🔇 Additional comments (4)
tests/test_shell_interactive.py (1)

108-108: LGTM! The explicit max_workers setting maintains test consistency.

The addition of max_workers=1 aligns with the test's existing sequential execution assumptions and matches the behavior in test_execute_single_task which uses cores=1.

Let's verify the sequential execution assumption is maintained across the codebase:

✅ Verification successful

The sequential execution assumption is consistently maintained across test files

The search results confirm that all test files consistently use max_workers=1 or max_cores=1 when testing sequential execution behavior. This includes:

  • test_shell_executor.py: Uses max_workers=1 for shell command execution tests
  • test_local_executor.py: Uses max_workers=1 for MPI executor tests
  • test_flux_executor.py: Uses max_workers=1 for Flux executor tests
  • test_executor_backend_*.py: Uses max_cores=1 for various backend tests
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Verify that other test files maintain consistent worker/core settings
# for sequential execution tests.

# Test: Search for similar test patterns that rely on sequential execution
rg -A 5 "test_.*executor.*" tests/ | rg -A 5 "max_workers.*=.*1|cores.*=.*1"

Length of output: 6774

tests/test_local_executor_future.py (1)

112-114: LGTM! Verify test coverage for multi-worker scenarios.

The addition of max_workers=1 ensures reliable test execution by controlling concurrency.

Let's verify if we have test coverage for multi-worker scenarios elsewhere:

✅ Verification successful

Multi-worker scenarios are well covered across test suite

The codebase has comprehensive test coverage for both single and multi-worker configurations:

  • test_executor_backend_mpi.py tests with max_workers=2
  • test_shell_executor.py includes tests with both max_workers=1 and max_workers=2
  • test_shared_input_check.py verifies various combinations of workers (1,2) and cores
  • test_local_executor.py and test_flux_executor.py both test with max_workers=2
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Check for tests using InteractiveExecutor with different worker configurations
# Expect: Mix of single and multi-worker test cases

rg -l "InteractiveExecutor\(" tests/
rg "max_workers\s*=\s*\d+" tests/

Length of output: 3377

tests/test_integration_pyiron_workflow.py (2)

Line range hint 1-238: Test implementation looks robust and well-structured.

The test class provides comprehensive coverage of executor behavior with dynamically defined objects, including:

  • Clear test scenarios and edge cases
  • Descriptive assertions with helpful messages
  • Proper handling of timeouts and exceptions

77-77: Consider if explicit max_workers=1 is necessary for these tests.

While the PR's objective is to make max_workers optional (defaulting to None), these tests explicitly set max_workers=1. This might be intentional for controlling concurrency in tests, but we should verify if it's necessary.

Let's analyze the test requirements:

Consider the following approaches:

  1. If sequential execution is required for these tests, document why max_workers=1 is necessary
  2. If parallel execution is safe, remove the explicit max_workers to test the default behavior
  3. Consider splitting timing-sensitive tests from those that can run in parallel

Also applies to: 107-107, 138-138, 162-162, 192-192, 221-221

✅ Verification successful

Setting max_workers=1 is necessary for these tests

The explicit max_workers=1 setting is required for these tests because:

  1. Tests contain timing-sensitive operations with sleep(0.1) calls and explicit timeout checks
  2. Tests verify state transitions through the running flag and callbacks
  3. Tests assert specific execution states that require deterministic behavior:
    • test_callable: Asserts that future is not done after submission
    • test_callback: Verifies state transitions from running to not running
    • test_timeout: Validates timeout behavior with precise timing

Removing the max_workers=1 constraint could lead to non-deterministic behavior and flaky tests due to race conditions in these timing-sensitive scenarios.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Check if these tests specifically need sequential execution
# Look for timing dependencies, race conditions, or order-specific assertions

# Search for sleep calls and timing-related assertions
echo "Checking timing dependencies:"
rg "sleep\(" tests/test_integration_pyiron_workflow.py -B 2 -A 2

# Search for assertions that might depend on execution order
echo "Checking order-dependent assertions:"
rg "assert.*running" tests/test_integration_pyiron_workflow.py -B 2 -A 2

Length of output: 907


Script:

#!/bin/bash
# Let's check the test cases and assertions to understand the execution requirements

# Check the test class and methods
echo "Test methods and their assertions:"
rg "def test_" tests/test_integration_pyiron_workflow.py -A 10

# Check for any parallel execution related code
echo "Parallel execution patterns:"
rg -i "parallel|concurrent|async|worker" tests/test_integration_pyiron_workflow.py

# Check the actual test implementations
echo "Full test implementations:"
cat tests/test_integration_pyiron_workflow.py

Length of output: 10211

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Outside diff range and nitpick comments (1)
tests/test_shared_input_check.py (1)

84-86: Consider adding more test cases for parameter combinations

While the current tests verify error cases, consider adding test cases for valid scenarios:

  1. Both parameters being None (expected success case)
  2. Both parameters being valid integers where max_workers * max_cores is valid

Example addition:

def test_check_max_workers_and_cores(self):
    # Existing error cases
    with self.assertRaises(ValueError):
        check_max_workers_and_cores(max_workers=2, max_cores=None)
    with self.assertRaises(ValueError):
        check_max_workers_and_cores(max_workers=None, max_cores=2)
    with self.assertRaises(ValueError):
        check_max_workers_and_cores(max_workers=2, max_cores=2)
    
    # Add success cases
    check_max_workers_and_cores(max_workers=None, max_cores=None)  # Should not raise
    check_max_workers_and_cores(max_workers=2, max_cores=4)  # Should not raise when product is valid
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between 10ffbf3 and b5c0a14.

📒 Files selected for processing (2)
  • executorlib/standalone/inputcheck.py (3 hunks)
  • tests/test_shared_input_check.py (3 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • executorlib/standalone/inputcheck.py
🔇 Additional comments (2)
tests/test_shared_input_check.py (2)

20-20: LGTM: Import follows existing conventions

The new import is properly grouped with related input check functions and follows the alphabetical ordering.


Line range hint 1-109: Verify test coverage for all related changes

The AI summary mentions changes to the Executor class and create_executor function. Let's verify that all these changes are properly tested.

Comment on lines +100 to +109
def test_validate_number_of_cores(self):
self.assertIsInstance(
validate_number_of_cores(max_cores=None, max_workers=None), int
)
self.assertIsInstance(
validate_number_of_cores(max_cores=1, max_workers=None), int
)
self.assertIsInstance(
validate_number_of_cores(max_cores=None, max_workers=1), int
)
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Enhance test coverage and documentation for validate_number_of_cores

While the test verifies return types, consider these improvements:

  1. Add a docstring explaining the test's purpose
  2. Add assertions for expected values, not just types
  3. Include error cases
  4. Test edge cases (e.g., negative values)

Example enhancement:

def test_validate_number_of_cores(self):
    """
    Test validate_number_of_cores function handles various input combinations
    and returns expected core counts.
    """
    # Test return types and values
    self.assertEqual(
        validate_number_of_cores(max_cores=None, max_workers=None),
        os.cpu_count(),  # Expected default
    )
    self.assertEqual(
        validate_number_of_cores(max_cores=2, max_workers=None),
        2,
    )
    self.assertEqual(
        validate_number_of_cores(max_cores=None, max_workers=2),
        os.cpu_count() // 2,  # Expected cores divided by workers
    )
    
    # Test error cases
    with self.assertRaises(ValueError):
        validate_number_of_cores(max_cores=-1, max_workers=None)
    with self.assertRaises(ValueError):
        validate_number_of_cores(max_cores=None, max_workers=-1)

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.

[feature] Make max_workers and max_cores optional parameters
1 participant