~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/config.py

  • Committer: John Arbash Meinel
  • Date: 2010-08-05 16:27:35 UTC
  • mto: This revision was merged to the branch mainline in revision 5374.
  • Revision ID: john@arbash-meinel.com-20100805162735-172opvx34sr5gpbl
Find a case where we are wasting a bit of memory.

Specifically the 'build_details' tuple contains a lot of wasted references,
and we hold on to one of these for each record we are fetching.
And for something like 'bzr pack', that is all keys.

For just loading all text build details on my bzr+ repository, With:
locations = b.repository.texts._index.get_build_details(b.repository.texts.keys())
This drops the memory consumption from:
WorkingSize   77604KiB
 to
WorkingSize   64640KiB

Or around 10.6MB. I worked it out to a savings of about 80 bytes/record
on data that can have hundreds of thousands of records (in 32-bit).

Show diffs side-by-side

added added

removed removed

Lines of Context:
65
65
import os
66
66
import sys
67
67
 
68
 
from bzrlib.decorators import needs_write_lock
69
68
from bzrlib.lazy_import import lazy_import
70
69
lazy_import(globals(), """
71
70
import errno
78
77
    atomicfile,
79
78
    debug,
80
79
    errors,
81
 
    lockdir,
82
80
    mail_client,
83
81
    osutils,
84
82
    registry,
85
83
    symbol_versioning,
86
84
    trace,
87
 
    transport,
88
85
    ui,
89
86
    urlutils,
90
87
    win32utils,
355
352
class IniBasedConfig(Config):
356
353
    """A configuration policy that draws from ini files."""
357
354
 
358
 
    def __init__(self, get_filename=symbol_versioning.DEPRECATED_PARAMETER,
359
 
                 file_name=None):
360
 
        """Base class for configuration files using an ini-like syntax.
361
 
 
362
 
        :param file_name: The configuration file path.
363
 
        """
 
355
    def __init__(self, get_filename):
364
356
        super(IniBasedConfig, self).__init__()
365
 
        self.file_name = file_name
366
 
        if symbol_versioning.deprecated_passed(get_filename):
367
 
            symbol_versioning.warn(
368
 
                'IniBasedConfig.__init__(get_filename) was deprecated in 2.3.'
369
 
                ' Use file_name instead.',
370
 
                DeprecationWarning,
371
 
                stacklevel=2)
372
 
            if get_filename is not None:
373
 
                self.file_name = get_filename()
374
 
        else:
375
 
            self.file_name = file_name
376
 
        self._content = None
 
357
        self._get_filename = get_filename
377
358
        self._parser = None
378
359
 
379
 
    @classmethod
380
 
    def from_string(cls, str_or_unicode, file_name=None, save=False):
381
 
        """Create a config object from a string.
382
 
 
383
 
        :param str_or_unicode: A string representing the file content. This will
384
 
            be utf-8 encoded.
385
 
 
386
 
        :param file_name: The configuration file path.
387
 
 
388
 
        :param _save: Whether the file should be saved upon creation.
389
 
        """
390
 
        conf = cls(file_name=file_name)
391
 
        conf._create_from_string(str_or_unicode, save)
392
 
        return conf
393
 
 
394
 
    def _create_from_string(self, str_or_unicode, save):
395
 
        self._content = StringIO(str_or_unicode.encode('utf-8'))
396
 
        # Some tests use in-memory configs, some other always need the config
397
 
        # file to exist on disk.
398
 
        if save:
399
 
            self._write_config_file()
400
 
 
401
 
    def _get_parser(self, file=symbol_versioning.DEPRECATED_PARAMETER):
 
360
    def _get_parser(self, file=None):
402
361
        if self._parser is not None:
403
362
            return self._parser
404
 
        if symbol_versioning.deprecated_passed(file):
405
 
            symbol_versioning.warn(
406
 
                'IniBasedConfig._get_parser(file=xxx) was deprecated in 2.3.'
407
 
                ' Use IniBasedConfig(_content=xxx) instead.',
408
 
                DeprecationWarning,
409
 
                stacklevel=2)
410
 
        if self._content is not None:
411
 
            co_input = self._content
412
 
        elif self.file_name is None:
413
 
            raise AssertionError('We have no content to create the config')
 
363
        if file is None:
 
364
            input = self._get_filename()
414
365
        else:
415
 
            co_input = self.file_name
 
366
            input = file
416
367
        try:
417
 
            self._parser = ConfigObj(co_input, encoding='utf-8')
 
368
            self._parser = ConfigObj(input, encoding='utf-8')
418
369
        except configobj.ConfigObjError, e:
419
370
            raise errors.ParseConfigError(e.errors, e.config.filename)
420
 
        # Make sure self.reload() will use the right file name
421
 
        self._parser.filename = self.file_name
422
371
        return self._parser
423
372
 
424
 
    def reload(self):
425
 
        """Reload the config file from disk."""
426
 
        if self.file_name is None:
427
 
            raise AssertionError('We need a file name to reload the config')
428
 
        if self._parser is not None:
429
 
            self._parser.reload()
430
 
 
431
373
    def _get_matching_sections(self):
432
374
        """Return an ordered list of (section_name, extra_path) pairs.
433
375
 
537
479
        return self.get_user_option('nickname')
538
480
 
539
481
    def _write_config_file(self):
540
 
        if self.file_name is None:
541
 
            raise AssertionError('We cannot save, self.file_name is None')
542
 
        conf_dir = os.path.dirname(self.file_name)
543
 
        ensure_config_dir_exists(conf_dir)
544
 
        atomic_file = atomicfile.AtomicFile(self.file_name)
 
482
        filename = self._get_filename()
 
483
        atomic_file = atomicfile.AtomicFile(filename)
545
484
        self._get_parser().write(atomic_file)
546
485
        atomic_file.commit()
547
486
        atomic_file.close()
548
 
        osutils.copy_ownership_from_path(self.file_name)
549
 
 
550
 
 
551
 
class LockableConfig(IniBasedConfig):
552
 
    """A configuration needing explicit locking for access.
553
 
 
554
 
    If several processes try to write the config file, the accesses need to be
555
 
    serialized.
556
 
 
557
 
    Daughter classes should decorate all methods that update a config with the
558
 
    ``@needs_write_lock`` decorator (they call, directly or indirectly, the
559
 
    ``_write_config_file()`` method. These methods (typically ``set_option()``
560
 
    and variants must reload the config file from disk before calling
561
 
    ``_write_config_file()``), this can be achieved by calling the
562
 
    ``self.reload()`` method. Note that the lock scope should cover both the
563
 
    reading and the writing of the config file which is why the decorator can't
564
 
    be applied to ``_write_config_file()`` only.
565
 
 
566
 
    This should be enough to implement the following logic:
567
 
    - lock for exclusive write access,
568
 
    - reload the config file from disk,
569
 
    - set the new value
570
 
    - unlock
571
 
 
572
 
    This logic guarantees that a writer can update a value without erasing an
573
 
    update made by another writer.
574
 
    """
575
 
 
576
 
    lock_name = 'lock'
577
 
 
578
 
    def __init__(self, file_name):
579
 
        super(LockableConfig, self).__init__(file_name=file_name)
580
 
        self.dir = osutils.dirname(osutils.safe_unicode(self.file_name))
581
 
        self.transport = transport.get_transport(self.dir)
582
 
        self._lock = lockdir.LockDir(self.transport, 'lock')
583
 
 
584
 
    def _create_from_string(self, unicode_bytes, save):
585
 
        super(LockableConfig, self)._create_from_string(unicode_bytes, False)
586
 
        if save:
587
 
            # We need to handle the saving here (as opposed to IniBasedConfig)
588
 
            # to be able to lock
589
 
            self.lock_write()
590
 
            self._write_config_file()
591
 
            self.unlock()
592
 
 
593
 
    def lock_write(self, token=None):
594
 
        """Takes a write lock in the directory containing the config file.
595
 
 
596
 
        If the directory doesn't exist it is created.
597
 
        """
598
 
        ensure_config_dir_exists(self.dir)
599
 
        return self._lock.lock_write(token)
600
 
 
601
 
    def unlock(self):
602
 
        self._lock.unlock()
603
 
 
604
 
    def break_lock(self):
605
 
        self._lock.break_lock()
606
 
 
607
 
    def _write_config_file(self):
608
 
        if self._lock is None or not self._lock.is_held:
609
 
            # NB: if the following exception is raised it probably means a
610
 
            # missing @needs_write_lock decorator on one of the callers.
611
 
            raise errors.ObjectNotLocked(self)
612
 
        super(LockableConfig, self)._write_config_file()
613
 
 
614
 
 
615
 
class GlobalConfig(LockableConfig):
 
487
        osutils.copy_ownership_from_path(filename)
 
488
 
 
489
 
 
490
class GlobalConfig(IniBasedConfig):
616
491
    """The configuration that should be used for a specific location."""
617
492
 
618
 
    def __init__(self):
619
 
        super(GlobalConfig, self).__init__(file_name=config_filename())
620
 
 
621
 
    @classmethod
622
 
    def from_string(cls, str_or_unicode, save=False):
623
 
        """Create a config object from a string.
624
 
 
625
 
        :param str_or_unicode: A string representing the file content. This
626
 
            will be utf-8 encoded.
627
 
 
628
 
        :param save: Whether the file should be saved upon creation.
629
 
        """
630
 
        conf = cls()
631
 
        conf._create_from_string(str_or_unicode, save)
632
 
        return conf
633
 
 
634
493
    def get_editor(self):
635
494
        return self._get_user_option('editor')
636
495
 
637
 
    @needs_write_lock
 
496
    def __init__(self):
 
497
        super(GlobalConfig, self).__init__(config_filename)
 
498
 
638
499
    def set_user_option(self, option, value):
639
500
        """Save option and its value in the configuration."""
640
501
        self._set_option(option, value, 'DEFAULT')
646
507
        else:
647
508
            return {}
648
509
 
649
 
    @needs_write_lock
650
510
    def set_alias(self, alias_name, alias_command):
651
511
        """Save the alias in the configuration."""
652
512
        self._set_option(alias_name, alias_command, 'ALIASES')
653
513
 
654
 
    @needs_write_lock
655
514
    def unset_alias(self, alias_name):
656
515
        """Unset an existing alias."""
657
 
        self.reload()
658
516
        aliases = self._get_parser().get('ALIASES')
659
517
        if not aliases or alias_name not in aliases:
660
518
            raise errors.NoSuchAlias(alias_name)
662
520
        self._write_config_file()
663
521
 
664
522
    def _set_option(self, option, value, section):
665
 
        self.reload()
 
523
        # FIXME: RBC 20051029 This should refresh the parser and also take a
 
524
        # file lock on bazaar.conf.
 
525
        conf_dir = os.path.dirname(self._get_filename())
 
526
        ensure_config_dir_exists(conf_dir)
666
527
        self._get_parser().setdefault(section, {})[option] = value
667
528
        self._write_config_file()
668
529
 
669
530
 
670
 
class LocationConfig(LockableConfig):
 
531
class LocationConfig(IniBasedConfig):
671
532
    """A configuration object that gives the policy for a location."""
672
533
 
673
534
    def __init__(self, location):
674
 
        super(LocationConfig, self).__init__(
675
 
            file_name=locations_config_filename())
 
535
        name_generator = locations_config_filename
 
536
        if (not os.path.exists(name_generator()) and
 
537
                os.path.exists(branches_config_filename())):
 
538
            if sys.platform == 'win32':
 
539
                trace.warning('Please rename %s to %s'
 
540
                              % (branches_config_filename(),
 
541
                                 locations_config_filename()))
 
542
            else:
 
543
                trace.warning('Please rename ~/.bazaar/branches.conf'
 
544
                              ' to ~/.bazaar/locations.conf')
 
545
            name_generator = branches_config_filename
 
546
        super(LocationConfig, self).__init__(name_generator)
676
547
        # local file locations are looked up by local path, rather than
677
548
        # by file url. This is because the config file is a user
678
549
        # file, and we would rather not expose the user to file urls.
680
551
            location = urlutils.local_path_from_url(location)
681
552
        self.location = location
682
553
 
683
 
    @classmethod
684
 
    def from_string(cls, str_or_unicode, location, save=False):
685
 
        """Create a config object from a string.
686
 
 
687
 
        :param str_or_unicode: A string representing the file content. This will
688
 
            be utf-8 encoded.
689
 
 
690
 
        :param location: The location url to filter the configuration.
691
 
 
692
 
        :param save: Whether the file should be saved upon creation.
693
 
        """
694
 
        conf = cls(location)
695
 
        conf._create_from_string(str_or_unicode, save)
696
 
        return conf
697
 
 
698
554
    def _get_matching_sections(self):
699
555
        """Return an ordered list of section names matching this location."""
700
556
        sections = self._get_parser()
788
644
            if policy_key in self._get_parser()[section]:
789
645
                del self._get_parser()[section][policy_key]
790
646
 
791
 
    @needs_write_lock
792
647
    def set_user_option(self, option, value, store=STORE_LOCATION):
793
648
        """Save option and its value in the configuration."""
794
649
        if store not in [STORE_LOCATION,
796
651
                         STORE_LOCATION_APPENDPATH]:
797
652
            raise ValueError('bad storage policy %r for %r' %
798
653
                (store, option))
799
 
        self.reload()
 
654
        # FIXME: RBC 20051029 This should refresh the parser and also take a
 
655
        # file lock on locations.conf.
 
656
        conf_dir = os.path.dirname(self._get_filename())
 
657
        ensure_config_dir_exists(conf_dir)
800
658
        location = self.location
801
659
        if location.endswith('/'):
802
660
            location = location[:-1]
803
 
        parser = self._get_parser()
804
 
        if not location in parser and not location + '/' in parser:
805
 
            parser[location] = {}
806
 
        elif location + '/' in parser:
 
661
        if (not location in self._get_parser() and
 
662
            not location + '/' in self._get_parser()):
 
663
            self._get_parser()[location]={}
 
664
        elif location + '/' in self._get_parser():
807
665
            location = location + '/'
808
 
        parser[location][option]=value
 
666
        self._get_parser()[location][option]=value
809
667
        # the allowed values of store match the config policies
810
668
        self._set_option_policy(location, option, store)
811
669
        self._write_config_file()
814
672
class BranchConfig(Config):
815
673
    """A configuration object giving the policy for a branch."""
816
674
 
817
 
    def __init__(self, branch):
818
 
        super(BranchConfig, self).__init__()
819
 
        self._location_config = None
820
 
        self._branch_data_config = None
821
 
        self._global_config = None
822
 
        self.branch = branch
823
 
        self.option_sources = (self._get_location_config,
824
 
                               self._get_branch_data_config,
825
 
                               self._get_global_config)
826
 
 
827
675
    def _get_branch_data_config(self):
828
676
        if self._branch_data_config is None:
829
677
            self._branch_data_config = TreeConfig(self.branch)
927
775
        """See Config.gpg_signing_command."""
928
776
        return self._get_safe_value('_gpg_signing_command')
929
777
 
 
778
    def __init__(self, branch):
 
779
        super(BranchConfig, self).__init__()
 
780
        self._location_config = None
 
781
        self._branch_data_config = None
 
782
        self._global_config = None
 
783
        self.branch = branch
 
784
        self.option_sources = (self._get_location_config,
 
785
                               self._get_branch_data_config,
 
786
                               self._get_global_config)
 
787
 
930
788
    def _post_commit(self):
931
789
        """See Config.post_commit."""
932
790
        return self._get_safe_value('_post_commit')
996
854
    return osutils.pathjoin(config_dir(), 'bazaar.conf')
997
855
 
998
856
 
 
857
def branches_config_filename():
 
858
    """Return per-user configuration ini file filename."""
 
859
    return osutils.pathjoin(config_dir(), 'branches.conf')
 
860
 
 
861
 
999
862
def locations_config_filename():
1000
863
    """Return per-user configuration ini file filename."""
1001
864
    return osutils.pathjoin(config_dir(), 'locations.conf')