diff --git a/b2/bucket.py b/b2/bucket.py
index 9347570..6d4332a 100644
--- a/b2/bucket.py
+++ b/b2/bucket.py
@@ -34,7 +34,7 @@ class LargeFileUploadState(object):
     """
 
     def __init__(self, file_progress_listener):
-        self.lock = threading.Lock()
+        self.lock = threading.RLock()
         self.error_message = None
         self.file_progress_listener = file_progress_listener
         self.part_number_to_part_state = {}
@@ -48,6 +48,11 @@ class LargeFileUploadState(object):
         with self.lock:
             return self.error_message is not None
 
+    def get_error_message(self):
+        with self.lock:
+            assert self.has_error()
+            return self.error_message
+
     def update_part_bytes(self, bytes_delta):
         with self.lock:
             self.bytes_completed += bytes_delta

LargeFileUploadState object has no attribute get_error_message
In my backup log file I saw this line:

`b2_upload(/backup/#########.tar.gz,#########.tar.gz, 1466468375649): AttributeError("'LargeFileUploadState' object has no attribute 'get_error_message'",) 'LargeFileUploadState' object has no attribute 'get_error_message'`

No retry attempt were made and the file failed to upload.

(the ### were intentional and not the real file name)
diff --git a/test/test_bucket.py b/test/test_bucket.py
index 766af3c..44cb2e2 100644
--- a/test/test_bucket.py
+++ b/test/test_bucket.py
@@ -18,8 +18,9 @@ import six
 
 from b2.account_info import StubAccountInfo
 from b2.api import B2Api
+from b2.bucket import LargeFileUploadState
 from b2.download_dest import DownloadDestBytes
-from b2.exception import B2Error, InvalidAuthToken, MaxRetriesExceeded
+from b2.exception import AlreadyFailed, B2Error, InvalidAuthToken, MaxRetriesExceeded
 from b2.file_version import FileVersionInfo
 from b2.part import Part
 from b2.progress import AbstractProgressListener
@@ -146,6 +147,22 @@ class TestListParts(TestCaseWithBucket):
         self.assertEqual(expected_parts, list(self.bucket.list_parts(file1.file_id, batch_size=1)))
 
 
+class TestUploadPart(TestCaseWithBucket):
+    def test_error_in_state(self):
+        file1 = self.bucket.start_large_file('file1.txt', 'text/plain', {})
+        content = six.b('hello world')
+        file_progress_listener = mock.MagicMock()
+        large_file_upload_state = LargeFileUploadState(file_progress_listener)
+        large_file_upload_state.set_error('test error')
+        try:
+            self.bucket._upload_part(
+                file1.file_id, 1, (0, 11), UploadSourceBytes(content), large_file_upload_state
+            )
+            self.fail('should have thrown')
+        except AlreadyFailed:
+            pass
+
+
 class TestListUnfinished(TestCaseWithBucket):
     def test_empty(self):
         self.assertEqual([], list(self.bucket.list_unfinished_large_files()))
diff --git a/test_b2_command_line.py b/test_b2_command_line.py
index 7cadd76..8d23678 100644
--- a/test_b2_command_line.py
+++ b/test_b2_command_line.py
@@ -319,8 +319,8 @@ def basic_test(b2_tool, bucket_name):
 
     file_to_upload = 'README.md'
 
-    with open(file_to_upload, 'rb') as f:
-        hex_sha1 = hashlib.sha1(f.read()).hexdigest()
+    hex_sha1 = hashlib.sha1(read_file(file_to_upload)).hexdigest()
+
     uploaded_a = b2_tool.should_succeed_json(
         [
             'upload_file', '--noProgress', '--quiet', bucket_name, file_to_upload, 'a'

pip install -e .
parse_log_pytest
requirements.txt
nose yapf pyflakes pytest
3.5
requirements.txt
pytest --no-header -rA --tb=line --color=no -p no:cacheprovider -W ignore::DeprecationWarning
attrs==22.2.0
-e git+https://github.com/Backblaze/B2_Command_Line_Tool.git@761d24c8dbd00f94decbf14cf0136de0a0d9f054#egg=b2
certifi==2021.5.30
charset-normalizer==2.0.12
idna==3.10
importlib-metadata==4.8.3
importlib-resources==5.4.0
iniconfig==1.1.1
nose==1.3.7
packaging==21.3
pluggy==1.0.0
py==1.11.0
pyflakes==3.0.1
pyparsing==3.1.4
pytest==7.0.1
requests==2.27.1
six==1.17.0
tomli==1.2.3
tqdm==4.64.1
typing_extensions==4.1.1
urllib3==1.26.20
yapf==0.32.0
zipp==3.6.0

name: B2_Command_Line_Tool
channels:
  - defaults
  - https://repo.anaconda.com/pkgs/main
  - https://repo.anaconda.com/pkgs/r
  - conda-forge
dependencies:
  - _libgcc_mutex=0.1=main
  - _openmp_mutex=5.1=1_gnu
  - ca-certificates=2025.2.25=h06a4308_0
  - certifi=2021.5.30=py36h06a4308_0
  - ld_impl_linux-64=2.40=h12ee557_0
  - libffi=3.3=he6710b0_2
  - libgcc-ng=11.2.0=h1234567_1
  - libgomp=11.2.0=h1234567_1
  - libstdcxx-ng=11.2.0=h1234567_1
  - ncurses=6.4=h6a678d5_0
  - openssl=1.1.1w=h7f8727e_0
  - pip=21.2.2=py36h06a4308_0
  - python=3.6.13=h12debd9_1
  - readline=8.2=h5eee18b_0
  - setuptools=58.0.4=py36h06a4308_0
  - sqlite=3.45.3=h5eee18b_0
  - tk=8.6.14=h39e8969_0
  - wheel=0.37.1=pyhd3eb1b0_0
  - xz=5.6.4=h5eee18b_1
  - zlib=1.2.13=h5eee18b_1
  - pip:
      - attrs==22.2.0
      - charset-normalizer==2.0.12
      - idna==3.10
      - importlib-metadata==4.8.3
      - importlib-resources==5.4.0
      - iniconfig==1.1.1
      - nose==1.3.7
      - packaging==21.3
      - pluggy==1.0.0
      - py==1.11.0
      - pyflakes==3.0.1
      - pyparsing==3.1.4
      - pytest==7.0.1
      - requests==2.27.1
      - six==1.17.0
      - tomli==1.2.3
      - tqdm==4.64.1
      - typing-extensions==4.1.1
      - urllib3==1.26.20
      - yapf==0.32.0
      - zipp==3.6.0
prefix: /opt/conda/envs/B2_Command_Line_Tool

test/test_bucket.py::TestUploadPart::test_error_in_state
test/test_bucket.py::TestReauthorization::testCreateBucket test/test_bucket.py::TestListParts::testEmpty test/test_bucket.py::TestListParts::testThree test/test_bucket.py::TestListUnfinished::test_empty test/test_bucket.py::TestListUnfinished::test_one test/test_bucket.py::TestListUnfinished::test_three test/test_bucket.py::TestLs::test_empty test/test_bucket.py::TestLs::test_hidden_file test/test_bucket.py::TestLs::test_one_file_at_root test/test_bucket.py::TestLs::test_started_large_file test/test_bucket.py::TestLs::test_three_files_at_root test/test_bucket.py::TestLs::test_three_files_in_dir test/test_bucket.py::TestLs::test_three_files_multiple_versions test/test_bucket.py::TestUpload::test_upload_bytes test/test_bucket.py::TestUpload::test_upload_bytes_progress test/test_bucket.py::TestUpload::test_upload_file_one_fatal_error test/test_bucket.py::TestUpload::test_upload_file_too_many_retryable_errors test/test_bucket.py::TestUpload::test_upload_large test/test_bucket.py::TestUpload::test_upload_large_resume test/test_bucket.py::TestUpload::test_upload_large_resume_all_parts_there test/test_bucket.py::TestUpload::test_upload_large_resume_file_info test/test_bucket.py::TestUpload::test_upload_large_resume_file_info_does_not_match test/test_bucket.py::TestUpload::test_upload_large_resume_no_parts test/test_bucket.py::TestUpload::test_upload_large_resume_part_does_not_match test/test_bucket.py::TestUpload::test_upload_large_resume_wrong_part_size test/test_bucket.py::TestUpload::test_upload_local_file test/test_bucket.py::TestUpload::test_upload_one_retryable_error test/test_bucket.py::TestDownload::test_download_by_id_no_progress test/test_bucket.py::TestDownload::test_download_by_id_progress test/test_bucket.py::TestDownload::test_download_by_name_no_progress test/test_bucket.py::TestDownload::test_download_by_name_progress test_b2_command_line.py::TestCommandLine::test_stderr_patterns
b2/bucket.py
b2/bucket.py