Search Results: "morph"

16 July 2011

Matthew Palmer: MySQL replication and crash recovery

The question was recently asked, How do I perform crash recovery on a MySQL master/slave database cluster? Because replication is async, a transaction that has been committed to the master may not be able to leave the master before the crash happens. The short answer is, you don t. Asynchronous replication, by it s very nature, is prone to these sorts of issues, and MySQL doesn t make life easier in general by using query replay. Some of the issues aren t massive: for instance, if a slave crashes, the master will keep replication logs for the slave until it comes back up (as long as the slave comes back before the master decides some of the replogs are too old and starts deleting them, of course), so as long as you can live without a master for a while, you can recover and continue along your merry way. Promoting a slave to be a master, on the other hand, is a situation frought with peril. Here, you ve just got to hope hope that your chosen slave is consistent with the master s state at death (because it s async, you have no guarantees about that), and that all the other slaves have the same ideas about what counts as the current state of the data. If your newly-promoted slave managed to apply an update that another slave didn t, that slave will be out of sync with reality (until such time as the now-dead master comes back and replays all those queries hahahaha). To even guarantee that any slaves have a consistent view of the data as compared to the new master, you ve got to rereplicate everything from the new master, because MySQL s where are you up to? counter is master-specific. I ve heard people who have to deal with this sort of thing these days say that the Maatkit tools are handy for dealing with various issues of this type (monitoring, repairing the database when it gets inconsistent). However, I prefer to keep right the hell away from MySQL replication altogether, after a year of having to gently coax an insane MySQL tree replication setup to do the right thing for any extended period, and watching a group of DBAs at Engine Yard go slowly insane. My preferred alternatives are: Never discount the value of the Jaws approach to scaling ( we re gonna need a bigger boat^Wmachine ) hardware is so ridiculously cheap, relative to the clueful sysadmin time needed to make (and keep) MySQL replication running, that you really need to be at the huge end of town (where other scaling issues are going to bite you in the arse first) before spending too much time on your database is worth the investment. Even then, you ll get more bang for your buck from: than you ever will from the constant, mind-numbing grind that is keeping MySQL replication going.
  1. The term comes from a time when a colleague near-screamed This slave cluster keeps fucking me! at a particularly intransigent set of MySQL slaves.

27 May 2011

Tanguy Ortolo: Wrapper to use su as sudo (or sudo as su)

The problem Sudo logo: a shield with a hash sign, a lightning and SUDO written on Some programs require root privileges for part of their work: most notably Debian building tools such as dpkg-buildpackage or pdebuild. So, several command can be used to obtain real or fake root privileges: su, sudo, fakeroot, etc. Among them, su is quite distinct, because it only takes a quoted command to pass it to a shell. That is why its interface is completely incompatible with the others':
$ su -c "pbuilder --build package.dsc"
$ sudo pbuild --build package.dsc
This is annoying when real root privileges are needed, for instance by pbuilder, a tool to build Debian packages inside a minimal chroot. Indeed, in such a case I only know one command to obtain the root privileges, sudo, and some people (read: me) prefer using su.A wrapper Long story short: as I saw no existing tool to do that (but I would be glad to hear if there was already one), I wrote a wrapper that emulates a (very basic) sudo-like interface by using su, and vice-versa. It is available from my Git repository:
$ git clone git://git.ortolo.eu/sudo-su.git
It is polymorphic, BusyBox-style: call it sudo-su and it will emulate sudo with su; call it su-sudo and it will emulate su with sudo. For instance:
$ pdebuild --pbuilderroot /usr/local/bin/sudo-su
It is WTFPL-licensed, it implements no options from sudo and su (except sudo's -c, of course), but I can do something better if it may be useful to other people.

23 February 2011

Andrew McMillan: Status: Busy

I've been very quiet on here for a few months, and the reason is (of course) because I've been doing other things. No surprise there, I guess! What I've been doing, first of all, is writing aCal, an Android CalDAV client with a lot of help from Chris Noldus. It's probably fair to say that Chris did most of the heavy lifting while I learned Java, did most of the UI & graphic design, and the low-level CalDAV synchronisation and geeky calendaring code. This is now available in the Android market for NZD$2.00 to help us fund further development and in a few days (or a couple of weeks) I'll get the source code up somewhere public (probably Gitorious) and it will be licensed GPLv3... When I get a moment - although refer to title :-) Of course on top of that I'd already committed to a second year of handling the sponsorship job for Linux.Conf.AU, so a fair amount of January was taken up with preparing for, and attending LCA in Brisbane where I took a lot of photos. LCA was, as always, a blast, and all credit to the fantastic team over in Brisbane who really came together when it seemed it was all going to fall apart after the flooding. This seems to be six long months of disasters around this part of the world, although until Tuesday I guess we thought we'd escaped the worst of it in New Zealand. After LCA I snuck home for a few days before flying off in the other direction, to UC Berkeley, for CalConnect XX where I managed to get in some great testing of the new Android CalDAV client, some excellent standards discussions, more photos and (even) a few days sightseeing. I've been home for a week from that now - just barely long enough to hug the kids, really - and today I am in the air once more, off to CeBIT in Hannover where I have been lucky to be sponsored a free booth in the Open Source Lounge by Linux New Media AG. My original travel sponsor slipped through the cracks, but I was fortunate that InternetNZ agreed to help fund me instead. If you're coming to CeBIT please do stop by the Morphoss table in the Open Source Lounge (Hall 2, F40) for a chat about DAViCal and Android - and anything else you want to talk about! I'll be there all week :-) And if I have some quiet time in that week (yeah, right!) I'll be trying to push out a new DAViCal release, which is overdue really, since the last one was two months ago.

10 February 2011

Marco Silva: Using Horizontal Sharding in SQLAlchemy to display multiple DBs

My problem was: I had a number of databases generated in different machines and I wanted to query them as if they were one, using the database the data came from as a field while querying and while showing results. The databases are SQLite3 files, generated using SQLAlchemy in a Python program. I solved this by using SQLAlchemy, which was good because I could use the same ORM mapping that the program used. I noticed that the Horizontal Sharding SQLAlchemy extension would fit well the problem, although not perfectly. I had to make some changes in some classes of this extension, and now it works fine. It was possible to filter the data using database as a criteria, but I couldn't get the database information from each line of a query result. I made a simple patch to SQLAlchemy, which wasn't likely to be introduced in the distribution, but worked for me, and sent it to its bug tracker. The change was included in SQLAlchemy in a very different fashion, as expected, but since I'm using the released version from SQLAlchemy, I kept on using my version of the patch. I don't want to do direct changes in SQLAlchemy source code, so I made the change in my program:
class ShardedSessionShardId(ShardedSession):
    def __init__(self, *args, **kwargs):
        super(ShardedSessionShardId, self).__init__(*args, **kwargs)
        self._query_cls = ShardedQueryShardId
class ShardedQueryShardId(ShardedQuery):
    def _execute_and_instances(self, context):
        if self._shard_id is not None:
            result = self.session.connection(
                            mapper=self._mapper_zero(),
                            shard_id=self._shard_id).execute(context.statement, self._params)
            news = list(self.instances(result, context))
            for new in news:
                new.shard_id = self._shard_id
            return iter(news)
        else:
            partial = []
            for shard_id in self.query_chooser(self):
                result = self.session.connection(
                            mapper=self._mapper_zero(),
                            shard_id=shard_id).execute(context.statement, self._params)
                news = list(self.instances(result, context))
                for new in news:
                    new.shard_id = shard_id
                partial = partial + news
            # if some kind of in memory 'sorting'
            # were done, this is where it would happen
            return iter(partial)
create_session = sessionmaker(class_=ShardedSessionShardId)
Another problem is that I had to make each result be included in the query, even if two results from different DBs have the same primary key. I achieved this by changing two classes: WeakInstanceDict, and Mapper. For using the new WeakInstanceDict, I had again to change the ShardedSession variation:
class WeakInstanceDictNoIdentity(WeakInstanceDict):
    def add(self, state):
        # if state.key in self:
        #     if dict.__getitem__(self, state.key) is not state:
        #         raise AssertionError("A conflicting state is already "
        #                             "present in the identity map for key %r"
        #                             % (state.key, ))
        # else:
            dict.__setitem__(self, state.key, state)
            self._manage_incoming_state(state)
class ShardedSessionShardId(ShardedSession):
    def __init__(self, *args, **kwargs):
        super(ShardedSessionShardId, self).__init__(*args, **kwargs)
        self._query_cls = ShardedQueryShardId
        self._identity_cls = WeakInstanceDictNoIdentity
        self.identity_map = self._identity_cls()
To start using the new Mapper, I simply replaced each call to mapper with MapperNoIdentity:
class MapperNoIdentity(Mapper):
    def _instance_processor(self, context, path, adapter,
                                polymorphic_from=None, extension=None,
                                only_load_props=None, refresh_state=None,
                                polymorphic_discriminator=None):
        """Produce a mapper level row processor callable
           which processes rows into mapped instances."""
        pk_cols = self.primary_key
        if polymorphic_from or refresh_state:
            polymorphic_on = None
        else:
            if polymorphic_discriminator is not None:
                polymorphic_on = polymorphic_discriminator
            else:
                polymorphic_on = self.polymorphic_on
            polymorphic_instances = util.PopulateDict(
                                        self._configure_subclass_mapper(
                                                context, path, adapter)
                                        )
        version_id_col = self.version_id_col
        if adapter:
            pk_cols = [adapter.columns[c] for c in pk_cols]
            if polymorphic_on is not None:
                polymorphic_on = adapter.columns[polymorphic_on]
            if version_id_col is not None:
                version_id_col = adapter.columns[version_id_col]
        identity_class = self._identity_class
        def identity_key(row):
            return identity_class, tuple([row[column] for column in pk_cols])
        new_populators = []
        existing_populators = []
        load_path = context.query._current_path + path
        def populate_state(state, dict_, row, isnew, only_load_props):
            if isnew:
                if context.propagate_options:
                    state.load_options = context.propagate_options
                if state.load_options:
                    state.load_path = load_path
            if not new_populators:
                new_populators[:], existing_populators[:] = \
                                    self._populators(context, path, row,
                                                        adapter)
            if isnew:
                populators = new_populators
            else:
                populators = existing_populators
            if only_load_props:
                populators = [p for p in populators
                                if p[0] in only_load_props]
            for key, populator in populators:
                populator(state, dict_, row)
        session_identity_map = context.session.identity_map
        if not extension:
            extension = self.extension
        translate_row = extension.get('translate_row', None)
        create_instance = extension.get('create_instance', None)
        populate_instance = extension.get('populate_instance', None)
        append_result = extension.get('append_result', None)
        populate_existing = context.populate_existing or self.always_refresh
        if self.allow_partial_pks:
            is_not_primary_key = _none_set.issuperset
        else:
            is_not_primary_key = _none_set.issubset
        def _instance(row, result):
            if translate_row:
                ret = translate_row(self, context, row)
                if ret is not EXT_CONTINUE:
                    row = ret
            if polymorphic_on is not None:
                discriminator = row[polymorphic_on]
                if discriminator is not None:
                    _instance = polymorphic_instances[discriminator]
                    if _instance:
                        return _instance(row, result)
            # determine identity key
            if refresh_state:
                identitykey = refresh_state.key
                if identitykey is None:
                    # super-rare condition; a refresh is being called
                    # on a non-instance-key instance; this is meant to only
                    # occur within a flush()
                    identitykey = self._identity_key_from_state(refresh_state)
            else:
                identitykey = identity_key(row)
            # instance = session_identity_map.get(identitykey)
            # if instance is not None:
            #     state = attributes.instance_state(instance)
            #     dict_ = attributes.instance_dict(instance)
            #     isnew = state.runid != context.runid
            #     currentload = not isnew
            #     loaded_instance = False
            #     if not currentload and \
            #             version_id_col is not None and \
            #             context.version_check and \
            #             self._get_state_attr_by_column(
            #                     state,
            #                     dict_,
            #                     self.version_id_col) != \
            #                             row[version_id_col]:
            #         raise orm_exc.ConcurrentModificationError(
            #                 "Instance '%s' version of %s does not match %s"
            #                 % (state_str(state),
            #                     self._get_state_attr_by_column(
            #                                 state, dict_,
            #                                 self.version_id_col),
            #                         row[version_id_col]))
            # elif refresh_state:
            if refresh_state:
                # out of band refresh_state detected (i.e. its not in the
                # session.identity_map) honor it anyway.  this can happen
                # if a _get() occurs within save_obj(), such as
                # when eager_defaults is True.
                state = refresh_state
                instance = state.obj()
                dict_ = attributes.instance_dict(instance)
                isnew = state.runid != context.runid
                currentload = True
                loaded_instance = False
            else:
                # check for non-NULL values in the primary key columns,
                # else no entity is returned for the row
                if is_not_primary_key(identitykey[1]):
                    return None
                isnew = True
                currentload = True
                loaded_instance = True
                if create_instance:
                    instance = create_instance(self,
                                                context,
                                                row, self.class_)
                    if instance is EXT_CONTINUE:
                        instance = self.class_manager.new_instance()
                    else:
                        manager = attributes.manager_of_class(
                                                instance.__class__)
                        # TODO: if manager is None, raise a friendly error
                        # about returning instances of unmapped types
                        manager.setup_instance(instance)
                else:
                    instance = self.class_manager.new_instance()
                dict_ = attributes.instance_dict(instance)
                state = attributes.instance_state(instance)
                state.key = identitykey
                # manually adding instance to session.  for a complete add,
                # session._finalize_loaded() must be called.
                state.session_id = context.session.hash_key
                session_identity_map.add(state)
            if currentload or populate_existing:
                if isnew:
                    state.runid = context.runid
                    context.progress[state] = dict_
                if not populate_instance or \
                        populate_instance(self, context, row, instance,
                            only_load_props=only_load_props,
                            instancekey=identitykey, isnew=isnew) is \
                            EXT_CONTINUE:
                    populate_state(state, dict_, row, isnew, only_load_props)
            else:
                # populate attributes on non-loading instances which have
                # been expired
                # TODO: apply eager loads to un-lazy loaded collections ?
                if state in context.partials or state.unloaded:
                    if state in context.partials:
                        isnew = False
                        (d_, attrs) = context.partials[state]
                    else:
                        isnew = True
                        attrs = state.unloaded
                        # allow query.instances to commit the subset of attrs
                        context.partials[state] = (dict_, attrs)
                    if not populate_instance or \
                            populate_instance(self, context, row, instance,
                                only_load_props=attrs,
                                instancekey=identitykey, isnew=isnew) is \
                                EXT_CONTINUE:
                        populate_state(state, dict_, row, isnew, attrs)
            if loaded_instance:
                state._run_on_load(instance)
            if result is not None and \
                        (not append_result or
                            append_result(self, context, row, instance,
                                    result, instancekey=identitykey,
                                    isnew=isnew)
                                    is EXT_CONTINUE):
                result.append(instance)
            return instance
        return _instance
I had to include some auxiliary definitions to make the rewrites work:
_none_set = frozenset([None])
_runid = 1L
_id_lock = util.threading.Lock()
def _new_runid():
    global _runid
    _id_lock.acquire()
    try:
        _runid += 1
        return _runid
    finally:
        _id_lock.release()
It would be good to be able to set these identity requirements as a parameter. My last problem was selecting more than one database to search. setshard only worked for one, so I created a new field in query, called shards, and checked for it on querychooser:
def query_chooser(query):
    try:
        return query.shards
    except AttributeError:
        pass
    return tcs.keys()
So, when I want to look only in a list of shards, I set this field. I'm aware that this is not a recommended python idiom, but, well, it works fine.

5 February 2011

Vincent Fourmond: ctioga superseded by ctioga2

First, some historical details: some 6 years ago, I discovered Ruby together with Tioga, a great library for producing plots. That immediately resulted in my first Ruby program, ctioga. But as years passed and more features were forced into ctioga, I was compelled to see my initial design mistakes, and think about a complete rewrite. This is where ctioga2 was born, some time at the beginning of 2009. I completely stopped using the old ctioga a few month after starting to work on ctioga2, but it took ages before I finally came up with a decent enough documentation to release and announce publicly. But now, it's done, and the first public release of ctioga2 is out, still fresh ! Here are a few highlights of the differences: In any case, you can make yourself an opinion of ctioga2 by looking at the galleries. Enjoy !

1 December 2010

Gintautas Miliauskas: Windows (II): User Interface

(This is a part of a series of posts on my recent experience with Windows. See Windows (I) for the first post.)

The Windows user interface is definitely acceptable. The charge that it is too colorful or toy-like is completely unfounded. Rather, perhaps geeks should spend less time looking at their gray Motif boxy controls. In terms of speed, the UI is generally more responsive than on Linux, though maybe less so than previous versions of Windows (that could be because my Intel video IGP is a slouch).

An interesting observation is that apps are almost as heterogeneous in terms of interface as in GNU/Linux, even though the standard controls are ubiquitous. Even apps by Microsoft can be separated into different "generations" of UI (e.g., folder windows, Control Panel, Microsoft Office). Also it is clear that generally more attention is paid to UI and usability by app developers, although perhaps not as much as in Apple products.

The greatest asset and the worst offender at the same time, by far, is the overall GUI orientation of the system. Needless to say, it aids discoverability, but reduces scripting capabilities. Problem is, even if you do not need to write scripts per se, command-line actions are useful because they can be repeated and chained very easily, using shell history. In Windows, I occasionally find myself doing much repetitive clicking that would likely be an "Alt-Tab Up Enter" (or sometimes just one key) sequence in Linux. Moreover, the lack of good standartized scripting is a huge pain during app deployments which tend be repetetive. I did not like Windows on servers before, and I do not like it now.

To be fair, my complaints about scripting capabilities may be partly moot because I have only used vanilla Windows batch files, and have not looked at all into Windows PowerShell, which is a new-generation scripting tool. The examples are fairly impressive. The language has a nice look and is clearly powerful, but also looks somewhat complicated which keeps me away until I find a good reason to learn it.

Initially I missed workspaces. The one app I found that was supposed to emulate workspaces would take several seconds to switch the workspace, so it was completely unusable. I learned to do without them rather quickly though. Actually I'm starting to think that using many workspaces is a sign that you are doing too many things at once and also they are an invitation to distraction (how many of you have a "blog reader" workspace right aside your "work" workspace, ready at your fingertips at a moment's notice?). Workspaces do have one killer feature: you can jump directly to a given workspace instead of cycling through windows. This is very useful when working with more than two apps, in which case without workspaces you are forced to think about the morphing Alt-Tab queue when switching windows.

Speaking about UI generations, I much prefer the ribbon toolbars of the new Office. The still-prevalent toolbars with zillions of old-style 32x32 (or 16x16) toolbar icons are really ugly. Typically only a few of the toolbar buttons are actually useful, and they make the user interface unnecessarily cramped and busy. The move to fewer and larger toolbar buttons is definitely on the right track.

The explorer context menus also tend to grow crazy long. Every app wants to get in there, and in the end you have a context menu that takes up half a screen vertically. Needless to say, most of those items are not used much. Sure, you can opt out during app installation, but at that time it is difficult to say how useful the context menu item will be, and there's no easy way (i.e. easier than just ignoring the cruft) to remove the entries afterwards.

In my coming posts I will cover package management and application development on Windows.

22 October 2010

Sandro Tosi: Scribe (& thrift) for Debian Squeeze

Some time ago, I asked what are the common solutions for services remote logging. It was suggested scribe, an application log server developed at Facebook and later released as an open source project, and we finally managed to get this ready for Debian squeeze.

Scribe requires a couple of other tools to work: thrift, a framework for services development (born at Facebook too), and thrift-fb303, a piece of thrift used as a bassline layer for Facebook services.

Searching the web for some work-in-progress of a Debian packaging, I found the wonderful work done by SimpleGeo; since I have some changes to do (and also take the occasion to play a bit with github), I forked their repos in these ones: scribe, thrift and thrift-fb303.

I've prepared a repository with those 3 packages, if you would like to try scribe, give it a look. They are for squeeze because, well, after all I need it for work and since it requires Python 2.6, using squeeze was the easiest choice (backporting 2.6 to lenny could be a mess, even if there are packages out there) and so I could also see how the upgrade from lenny would be.

Let's now dive into the doc, and some examples too, to find out the several configuration options and decide what's best for us.

6 October 2010

Andrew Cater: What you can learn from watching and listening

I'm in hospital, taking an enforced rest from work and normal life.with much too much time to think. Luckily I have relatively slow 'Net access.

I've just spent about three weeks in a specialist orthopaedic hospital which is also a centre of excellence, a research and teaching hospital. Some lessons for FLOSS:

Research methods matter: open communication matters more

The hospital was apparently doing various research projects, some minor, some major: whether cooling a knee joint in surgery improved recovery and aftercare, whether complete pain relief and comprehensive analgesia stimulated recovery, trying to standardise on a pain score to measure subjective pain - " On a scale of 1-10 how does it feel?"

The knee joint research was being written up and would be shared worldwide - if it proved worthwhile, hundreds of patients could benefit. If it wasn't worthwhile, it would still be written up just as comprehensively with testing methodology, evidence from double-blind trials or whatever as appropriate to save someone else from having to re-invent the wheel.

The analgesia study sounded just like the application of a doctor or nurse's intuitive commn sense and experience - but it was being carried out scientifically and measured. also answering supplementary questions like "If pain is sometimes a helpful indicator of problems, how should you guage reactions when all pain is dulled?" Additionally, the ward had dispensations to use different and more flexible drug regimes to include the use of oral morphine derivatives. Again, everything was being noted, measured and shared.

26 August 2010

Richard Darst: How DebConf manages money

One of the most debated points at the DebConf&Debian BOF was money management. Let's talk about how it works in an ideal world, and then how it tends to work in practice. This post only talks about year-to-year management, I hope for another post talking about the complexities of budgeting within a single year.In an ideal world, DebConf fundraises all the money it needs to go on. There is a DebConf sub-team for fundraising, since Debian doesn't have its own fundraising team. This team make brochures and solicits sponsors - there will be another post about this by someone on the fundraising team.We do our best to raise enough money to cover the conference, but it's long and hard work. In the months leading up to the conference, we are almost always running short. However, in the end, somehow, it comes through by a combination of cutting costs and continued fundraising work.The money is held by various nonprofits like SPI in the USA and FFIS in Europe. They hold it in trust for Debian. I think most people on the DebConf team agree that this money belongs to Debian, but is earmarked for DebConf since it was fundraised for DebConf. In the past, the DPL has not been asked for permission to use DebConf-fundraised money to put on a DebConf, since that's always been kind of obvious that it's for DebConf.Planning how much it will cost is hard. Venue costs aren't always known in advance, and we don't know our number of attendees until late. There are many uncertainties, which will be described later. These questions are hard to answer, but not impossible. Most of they time things work out in our favor.At the end of the conference, we tally everything up. Usually(=always, as far as I know), there is a surplus. This surplus is important, since DebConfN+1 usually needs a little seed money to get started before the new funds start rolling in. Historically, this seed money has been used for DebConfN+1, since it was raised earmarked for DebConf.In practice, it works out mostly like this. We keep track of the money, and try to stay within our budget, and cut things when we need to.For the last two years (coinciding with the recession), we have expected a deficit in the months and weeks leading up to DebConf. In these cases, we asked the DPL for some money to be given from Debian general funds for DebConf, and we provide background on our fundraising and budget status for use in making that decision. Both times, the Debian funds went to ensure that we could get travel sponsorship out. Debian general funds are not used without DPL approval.Also, keeping all the paperwork straight is a hard task. We have limited people, and the budget-trackers are always doing many other jobs, too. Personally, I'm not convinced that we always know how much the exact post-DebConf surplus is and where it is (SPI, FFIS, ...) each year. It sort of gets morphed into the general Debian money, and then we know "we have (about) XXX surplus" and then use that amount as our starting seed for the next year, taken from wherever is convenient. If Debian gives money to help make DebConf happen, it's usually as a loan. However, this loan isn't tracked carefully year to year. Someone looked up numbers during the BOF and reported that Debian has given/loaned 50,000 USD to DebConf over the years, but it's hard to know just how accurate that is. For comparison, the total DebConf budget over those years has been close to 1,000,000 USD, as best I can figure.We've tried various types of accounting software with mixed success. They are a lot of overhead, and we usually revert to keeping track of things in spreadsheets.
Our goals for budgeting (being self-sustaining) are good and well agreed on, but the implementation is hard.However, it's a place where it is easy for dedicated volunteers to get involved. If you wanted to help to make this better, you could do a lot of good by watching #debconf-team and lists, making careful records of our cash flow and where it comes from and goes to. All money we spend is announced and documented on the lists, so the information is there. To really do a complete job, this person would need to remain motivated in the months after DebConf and wrap up all of the final payments. Preferably, we'd have the same person year after year.There are many more suggestions that both others and I could make, but I'll save those for later.Tags: debconf

4 August 2010

Neil Williams: FreedomBox

Eben Moglen's talk at DebConf10 has inspired a flood of responses and activity at DebConf10. The original talk overran the allotted time within the venue and then outside the venue for something like an hour, HackLab discussions migrated to the single theme and brought in more and more people over lunch. When the Skype talk was left without a speaker in the afternoon, it morphed into a FreedomBox BOF with maybe 100 delegates planning and contributing to the ideas and another BOF is planned for Friday. A rough outline is appearing on the wiki page for those who want to follow it and the video of the original talk is available, linked from that Wiki page.

What gets people excited about this project is that we already have all the components to do the work, people have been doing most of this stuff at home, on their own, for some time and it needs to be brought together as a series of packages and meta packages and other middleware glue to get it working. (multistrap is part of that glue - it's already being used commercially in a similar role. Debian Pure Blends is another part of the glue. This can be a Debian solution, it doesn't have to be a separate project.)

The software and glue doesn't have to be dependent on a single hardware platform, this is about getting the software right and deploying it on whatever hardware can run it - albeit that the architecture itself is likely to be ARM-something.

It is time for social networking to be freed from the man-in-the-middle - time to withdraw user data from central servers owned by some third-party and share directly with your friends (whichever network they use). Aggregate all data from all your friends, keep all your data in your own plug and access it from wherever you are using whatever devices you have to hand. What's more, your friends - especially the non-techy friends - can just buy a cheap beige box that hides away in the corner of their home and have all the same access too - everyone gets to choose exactly which pieces of data get shared and to whom and can then use their friends plugs to store encrypted backups of their other data. Or maybe the final solution will be slightly different - it's up to people in Debian to make it happen. The tools are in our own hands, if we want this, we just need to do it.

What is needed now is some direction, there are choices to be made about how the glue is designed and how this is turned into a "self-discoverable" distributed network service. Yes, IPv6 will make this easier but only once ordinary users can deploy the device transparently and without configuration, so an interim solution will be needed.

Who's up for the challenge?

3 July 2010

Joachim Breitner: Free Groups Formalized

Since a few months, I have been playing around with Isabelle, a theorem prover system. I find it very intriguing to have proofs of mathematical statements checked by something as pendantic and comprehensive as a machine. Mathematicians always claim that their statements are true in all eternity, but the proofs are just checked by error-prone human beings. Especially with complex, large proofs that are only read by a handful of people, I doubt that these are always fully correct. Note that this does not imply that I doubt that the results are correct. They probably are. But a bit of doubt remains. A computer-checked proof, in contrast, can not accidentially omit corner cases, leave out seemingly trivial assumtions of used theorems or be misled by slightly differing definition from different sources. I was hoping to check at least parts of my diploma thesis using Isabelle, but it turns out that the standard algebra library shipped with Isabelle is not complete enough. Even free groups were missing. This was motivation enough to try to formalize them and prove the universal property and some isomorphisms (The free group over the empty set is the unit group, the free group over one generator is the additive group of integers and free groups over sets of same cardinality are isomophic). I submitted the resulting theory to the Archive of Formal Proofs and it was accepted. You can view the complete document or the document without proofs. I did not formalize the fact that isomorphic free groups have bases of same cardinality. As far as I know there is no simple argument that works directly with free groups. The proofs I have seen pass to the abelianization of the free group, i.e. the free module over and apply the well known proof from the analogous statement about vector spaces. But if someone knows an elementary proof of this fact, I d like to hear about it.

28 June 2010

Carl Chenet: Debian maintainer

As some of you may (or more certainly may not) have noticed reading the Debian Project News, I recently became a Debian maintainer. A hugh thanks to Sandro Tosi (morph) and Piotr O arowski (piotr) for their careful reviews of my work. And thanks to Raphael Hertzog (buxy) to pull the trigger for the beginning of my involvment in Debian. I can t wait to attend Debconf 2010. See you there ! Follow me on Identi.ca: http://identi.ca/carlchenet

6 May 2010

Andrew Pollock: [life] Crazy few days

Here's why: Zoe's birth Zoe Dianne Pollock was born at 12:30pm PDT, at Lucile Packard Children's Hospital, via Caesarean-section. Her birth weight was 2920 grams (6 lbs, 7 oz) and she's 49.5 centimetres (19.5 inches) long. As for why she was born 11 days early than her previously scheduled early arrival... Monday morning started off like any other normal Monday morning. I got up at 6am to go to the gym with Dan. I got home at around 8am. I asked Sarah if she'd felt any baby movement, and she said she hadn't. Even after some jiggling and a cold glass of orange juice, there was nothing. Not wanting a repeat performance of what happened last time, we wasted no time in heading to Labour and Delivery to get things checked out. When we were nearly at the hospital, Sarah felt some small movement, so that was a relief. When we got to the hospital, they performed a non-stress test as they have previously. They spent a good couple of hours with Sarah hooked up to the monitor, and then the on-call obstetrician asked us how we'd feel about just having the baby today. I asked if there was anything particularly concerning about the NST, and the OB said that she (the baby) was doing marvelously now, but was "less than marvelous" when we first came in. I haven't had a chance since to ask her if there was anything untoward observed during the delivery. Anyway, she (the on call OB) said that rather than have Sarah get stressed out for the following 11 days, she'd talked with our regular OB, and they felt that since she was at 37 weeks (and considered "full term") that it was okay to deliver at that point. At the end of the conversation, it was upgraded to a recommendation, rather than an option. So so much for an attempt at VBAC. We got to the hospital at around 9am, and I think they/we made the decision to deliver at about 10:30am. I had enough time to race home and grab the camera and race back again, and the next thing I knew we were all in the operating room, and Zoe was being delivered. It was amazing getting to see the "extraction". They dropped down the sheet that was blocking our view of the action right at the end, and I got to see (and photograph) Zoe's head and whole body being pulled out. She gave out a little cry, and Sarah and I totally lost it with relief. Things were a bit of a blur after that. I got to come over and meet Zoe while they were weighing her and measuring her, and then they bundled her up and let me take her back to Sarah's head-end on the operating table, so Sarah could get a look at her. Sarah handled this C-section better than last time. She didn't get any nausea, but she still got the itching and involuntary shivering, courtesy of the morphine. While they were still putting Sarah's insides back in, Zoe and I went to the nursery, where they put her under a heater for a little while to warm her up a bit. Then it was back to the recovery room to have a proper meeting with Sarah. Once Sarah was ready to move to the maternity ward, Zoe and I again returned to the nursery, where she got her first bath, had her umbilical cord trimmed some more, and her anti-theft device fitted to her umbilical cord clamp. Then it was off to the maternity ward. I think we were all in the maternity ward by 3pm. After a little while, we managed to get bumped to a private room, which was a lot better than the shared room (the other bed was vacant, but just having more space made a big difference), and that's where we've been ever since. I've stayed each night on the roll away bed, helping out with diaper (sorry Aussies, I've been here too long, and dealt with too many babies, I never think of them as "nappies" any more) changes, and burping and other miscellaneous tasks. Monday night was okay. Tuesday was a bit rough. Sarah didn't get any sleep on Monday night, and the anaesthesia totally wore off and she was in a fair bit of pain. She slept better on Tuesday night, and today, the breastfeeding has been going well, and the post-operative pain is well and truly under control. She's lost a bit of weight since delivery (as is to be expected). She was down to 5 lbs 14 oz (2664 grams) at last night's weigh-in, and at tonight's weigh-in she came in at 5 lbs 13 oz (2636 grams). So the weight loss is starting to plateau. We're already supplementing the breastfeeding with syringes of colostrum/milk that Sarah's pumped previously, so I hope the weight loss will have turned around by tomorrow night's weigh-in. The breastfeeding in general is going pretty well as of today. Mum and bub have both mastered the latch. I think it's just a matter of Sarah's milk coming in now. I've brought forward my 7 weeks paternity leave (an excellent benefit Google offers) to start as of Zoe's delivery. Sarah's Mum arrives on the 19th for 4 weeks, which will be great. We expect to be discharged from hospital on Friday. Luckily we were pretty much all ready for this happening a little bit earlier. We had our last baby classes on the weekend. The nursery is ready enough to bring a baby home to, so aside from Sarah's Mum being disappointed that Zoe's going to be a bit older when she first gets to see her, we're all good. The WiFi in the hospital is great. We've already Skyped with both grandmothers and they've had a chance to see Zoe. We've had a steady trickle of visitors, and the whole baby routine is making the time go pretty quickly.

23 April 2010

Joachim Breitner: Making dictionary passting explicit in Haskell

Haskell provides type classes to support polymorphism. A type class defines a few methods, which can then be implemented for a concrete type in the type class instance. This is a powerful system, but it also has it drawbacks. Most notably, each type can have at most one implementation of the type class. But sometimes you need to use a different implementation. If, for example, you used the Binary class to store data on disk. Now you changed your data type and the binary instance, and you can not read the old data any more. One solution is to re-name your type using newtype and implement another type instance for that. Often, this is enough. But still, instances are not first-class-citizens. You can not pass them around or modify them, as you can pass around and modify data and functions. Under the hood of the compiler, things look different. The ghc puts the methods of the instance in a dictionary and passes that implicitly to any functions having a (Class a) constraint. (Other implementations exist though) If one could make that behavior explicit, one could easily modify the instance before passing it to the function. But this is unfortunately not possible. But it is possible to pass an explicit dictionary along the data. I use the Monoid class as an example, and define a representation of the dictionary to-be-passed, as well as the dictionary of the default instance:
data MonoidDict a = MonoidDict
    ed_mempty :: a
  , ed_mappend :: a -> a -> a
   
monoidDict :: Monoid a => MonoidDict a
monoidDict = MonoidDict mempty mappend
(For conciseness, I ignore the mconcat method.) My first idea was to pass this instance along with data: (MonoidDict a, a). But this would not work because there are methods, such as mempty, who need the dictionary without getting passed a value to use. Therefore, I need to put the dictionary both in the covariant and the contravariant position:
newtype WithMonoidDict a = WithMonoidDict (MonoidDict a -> (MonoidDict a, a))
We need functions to clamp a dictionary to a value, and to extract it again:
wrapWithCustomMonoidDict :: MonoidDict a -> a -> WithMonoidDict a
wrapWithCustomMonoidDict dict val = WithMonoidDict $ const (dict, val)
extractFromCustomMonoidDict :: MonoidDict a -> WithMonoidDict a -> a
extractFromCustomMonoidDict dict (WithMonoidDict f) = snd (f dict)
Note that both expect the dictionary, so that it can be fed into WithMonoidDict from both sides . For convenience, we can define variants that use the standard instance:
wrapWithMonoidDict :: Monoid a => a -> WithMonoidDict a
wrapWithMonoidDict = wrapWithCustomMonoidDict monoidDict
extractFromMonoidDict :: Monoid a => WithMonoidDict a -> a
extractFromMonoidDict = extractFromCustomMonoidDict monoidDict
We want to be able to pass the wrapped values as any other value with a Monoid instance, so we need to declare that:
instance Monoid (WithMonoidDict a) where
  mempty = WithMonoidDict (\d -> (d, ed_mempty d))
  mappend (WithMonoidDict f1) (WithMonoidDict f2) = WithMonoidDict $ \d ->
  let (d1,v1) = f1 d
  (d2,v2) = f2 d
  in  (d1, ed_mappend d1 v1 v2)
Note that mappend has the choice between three dictionaries This is not a good sign, but let s hope that they are all the same. Does it work? Let s see:
listInstance :: MonoidDict [a]
listInstance = monoidDict
reverseInstance :: MonoidDict [a]
reverseInstance = monoidDict   ed_mappend = \l1 l2 -> l2 ++ l1  
examples = do
  let l1 = [1,2,3]
  let l2 = [4,5,6]
  putStrLn $ "Example lists: " ++ show l1 ++ " " ++ show l2
  putStrLn $ "l1 ++ l2: " ++ show (l1 ++ l2) 
  putStrLn $ "l1  mappend  l2: " ++ show (l1  mappend  l2) 
  putStrLn $ "Wrapped with default instance:"
  putStrLn $ "l1  mappend  l2: " ++ show (
  extractFromMonoidDict $ wrapWithMonoidDict l1  mappend  wrapWithMonoidDict l2)
  putStrLn $ "Same with reversed monoid instance:"
  putStrLn $ "l1  mappend  l2: " ++ show (
  extractFromCustomMonoidDict reverseInstance $
  wrapWithCustomMonoidDict reverseInstance l1  mappend 
  wrapWithCustomMonoidDict reverseInstance l2)
Running examples gives this output:
Example lists: [1,2,3] [4,5,6]
l1 ++ l2: [1,2,3,4,5,6]
l1  mappend  l2: [1,2,3,4,5,6]
Wrapped with default instance:
l1  mappend  l2: [1,2,3,4,5,6]
Same with reversed monoid instance:
l1  mappend  l2: [4,5,6,1,2,3]
Indeed it works. Unfortunately, this approach is not sufficient for all cases. It is perfectly valid to have functions with signature (Monoid a => Int -> Int). Obviously, this function would have no access to the explicit dictionary, because no value of type a is passed to or from it.<monoid a="a"> I wonder if it would be possible to extend the syntax somehow to be able to properly pass an alternative dictionary to such functions. But given that not all compilers use dictionary passing, my hopes are low.

Joachim Breitner: Making dictionary passing explicit in Haskell

Haskell provides type classes to support polymorphism. A type class defines a few methods, which can then be implemented for a concrete type in the type class instance. This is a powerful system, but it also has it drawbacks. Most notably, each type can have at most one implementation of the type class. But sometimes you need to use a different implementation. If, for example, you used the Binary class to store data on disk. Now you changed your data type and the binary instance, and you can not read the old data any more. One solution is to re-name your type using newtype and implement another type instance for that. Often, this is enough. But still, instances are not first-class-citizens. You can not pass them around or modify them, as you can pass around and modify data and functions. Under the hood of the compiler, things look different. The ghc puts the methods of the instance in a dictionary and passes that implicitly to any functions having a (Class a) constraint. (Other implementations exist though) If one could make that behavior explicit, one could easily modify the instance before passing it to the function. But this is unfortunately not possible. But it is possible to pass an explicit dictionary along the data. I use the Monoid class as an example, and define a representation of the dictionary to-be-passed, as well as the dictionary of the default instance:
data MonoidDict a = MonoidDict
    ed_mempty :: a
  , ed_mappend :: a -> a -> a
   
monoidDict :: Monoid a => MonoidDict a
monoidDict = MonoidDict mempty mappend
(For conciseness, I ignore the mconcat method.) My first idea was to pass this instance along with data: (MonoidDict a, a). But this would not work because there are methods, such as mempty, who need the dictionary without getting passed a value to use. Therefore, I need to put the dictionary both in the covariant and the contravariant position:
newtype WithMonoidDict a = WithMonoidDict (MonoidDict a -> (MonoidDict a, a))
We need functions to clamp a dictionary to a value, and to extract it again:
wrapWithCustomMonoidDict :: MonoidDict a -> a -> WithMonoidDict a
wrapWithCustomMonoidDict dict val = WithMonoidDict $ const (dict, val)
extractFromCustomMonoidDict :: MonoidDict a -> WithMonoidDict a -> a
extractFromCustomMonoidDict dict (WithMonoidDict f) = snd (f dict)
Note that both expect the dictionary, so that it can be fed into WithMonoidDict from both sides . For convenience, we can define variants that use the standard instance:
wrapWithMonoidDict :: Monoid a => a -> WithMonoidDict a
wrapWithMonoidDict = wrapWithCustomMonoidDict monoidDict
extractFromMonoidDict :: Monoid a => WithMonoidDict a -> a
extractFromMonoidDict = extractFromCustomMonoidDict monoidDict
We want to be able to pass the wrapped values as any other value with a Monoid instance, so we need to declare that:
instance Monoid (WithMonoidDict a) where
  mempty = WithMonoidDict (\d -> (d, ed_mempty d))
  mappend (WithMonoidDict f1) (WithMonoidDict f2) = WithMonoidDict $ \d ->
  let (d1,v1) = f1 d
  (d2,v2) = f2 d
  in  (d1, ed_mappend d1 v1 v2)
Note that mappend has the choice between three dictionaries This is not a good sign, but let s hope that they are all the same. Does it work? Let s see:
listInstance :: MonoidDict [a]
listInstance = monoidDict
reverseInstance :: MonoidDict [a]
reverseInstance = monoidDict   ed_mappend = \l1 l2 -> l2 ++ l1  
examples = do
  let l1 = [1,2,3]
  let l2 = [4,5,6]
  putStrLn $ "Example lists: " ++ show l1 ++ " " ++ show l2
  putStrLn $ "l1 ++ l2: " ++ show (l1 ++ l2) 
  putStrLn $ "l1  mappend  l2: " ++ show (l1  mappend  l2) 
  putStrLn $ "Wrapped with default instance:"
  putStrLn $ "l1  mappend  l2: " ++ show (
  extractFromMonoidDict $ wrapWithMonoidDict l1  mappend  wrapWithMonoidDict l2)
  putStrLn $ "Same with reversed monoid instance:"
  putStrLn $ "l1  mappend  l2: " ++ show (
  extractFromCustomMonoidDict reverseInstance $
  wrapWithCustomMonoidDict reverseInstance l1  mappend 
  wrapWithCustomMonoidDict reverseInstance l2)
Running examples gives this output:
Example lists: [1,2,3] [4,5,6]
l1 ++ l2: [1,2,3,4,5,6]
l1  mappend  l2: [1,2,3,4,5,6]
Wrapped with default instance:
l1  mappend  l2: [1,2,3,4,5,6]
Same with reversed monoid instance:
l1  mappend  l2: [4,5,6,1,2,3]
Indeed it works. Unfortunately, this approach is not sufficient for all cases. It is perfectly valid to have a function with signature (Monoid a => Maybe a -> Maybe a), whose behavior depends on the instance of a, even when being passed Nothing and returning Nothing. Such a function woul<monoid a="a">d have a problem here, because the dictionary would not be passed to the function.
</monoid> I wonder if it would be possible to extend the Haskell language somehow to be able to properly pass an alternative dictionary to such functions. But given that not all compilers use dictionary passing, my hopes are low.

15 March 2010

Jaldhar Vyas: 7DRL Challenge - Final days

I'm afraid that in the end I failed the challenge. As the last minutes of Saturday night ticked down, I was still mired in the minutiae of C++ Run-Time Type Identification (RTTI.) Sunday was my 12th wedding anniversary and I know from past experience that this is one day when it is advisable to step away from the computer. So I have to concede defeat for now. But I only actually spent about 48 hours on this project this week so if you really think about it, I still have 120 hours available. That's what I choose to believe anyway. The galling thing is in theory I know how this is supposed to work. A.L.F.s model (in the model-view-controller design pattern) is a class called World. World has an array of Rooms which represent rooms in the dungeon. In my original almost working version of a few days back, this Room class had all manner of verious members for different objects in the dungeon but I decided to redesign it the right way by consolidating all the redundant bits into an abstract base class called Item which would have concrete subclasses: Potion, Treasure, Monster etc. Room now only has to hold a pointer to the Item associated with that room. (Later on I will consider allowing multiple items per room but for now there can only be one.) A factory function creates Items and assigns them to each Room (or leaves them empty) It returns a pointer to an object of the appropriate class but we can access that object through the Item* because they are derived from Item. So far so good. The fun part happens when we want to know what the Item* really is. For example, combat makes sense for Monsters but not for Treasure. I only want to activate combat if the Item* is a Monster*. It's easy in theory:

if(dynamic_cast<Monster*>(contents))
    return FIGHT;
                            
...but it didn't work. Problem number 1. C++ RTTI only works on polymorphous classes. (Ones that contain atleast one virtual member.) So a little redesign of my classes was necessary. Problem #2, once you start slinging pointers around, segfaults soon follow. I had to go back and make absolutely sure I wasn't double-deleting objects and things of that nature. As I write this, I think I have finally got it all sorted out. So I am going to persevere and finish this game, its just going to take a bit longer than 7 days.

10 November 2009

Gunnar Wolf: PieSpy: Modelling IRC interactions

As some of you know, yesterday we started the two weeks of activities for the Fifth On-line Encounter of Free Education, Culture and Software. This year, the main topic for the conference is social networks, but not in the twitter-facebook sense Social networks as ways of understanding and modelling human-to-human interactions. Of course, there is interest from many attendees in using and taking advantage of said social network systems (and others), but the topic is far broader. One of the core tools of EDUSOL, and the only tool that is constantly active through our two weeks of activity, is IRC Which is somewhat of a challenge, as we receive some not-tech-savvy users from all over the world. The first year or two we asked everybody to connect via an IRC client (and we had even decided that it would be a technological filter We thought we would not cater for people without the technical skills to install and use IRC). Life proved us wrong, and we ended up using two web-to-IRC gateways: One of them, via an instance of CGI:IRC, and the other one via a third-party service, Mibbit. Now, the core mind behind EDUSOL is Alejandro Miranda, Pooka; I am not really into education (it is a very interesting topic but far from what I am good at). Pooka has been invovled in on-line communities analysis for several years already, since we worked together at FES Iztacala. For a long time he wanted to graph the interactions at EDUSOL, which was obviously possible given it was all centered on three tools (IRC for live chat, Drupal for generic information and Moodle for the talks and workshops' material This has changed this year, as we are giving much more weight to Drupal and taking it off Moodle), and so last year he was finally able to generate a EDUSOL 2008 conference photo (warning: 2MB image, 4311x3352px), where each person's avatar appears where most of his interactions were. This photo was (unfortunately) achieved using non-free software, but is very much looking at! Now, this year Pooka stumbled upon a great tool, PieSpy (available in Debian package piespy). Piespy generates very interesting interaction graph for IRC channels, as you can see here: Piespy is a bot that sits in the IRC channel logging everything that any given user "says" directly to another one, and graphs it. Not much to say here, only that it generates a very good (and practically real-time) view of what happens on the channel. For this particular channel, and during EDUSOL, you can see here the latest IRC snapshots. Very fun to see - and somewhat addictive how it morphs across time :)
AttachmentSize
EDUSOL 2008 - Conference photo1.97 MB
Current (Monday+Tuesday) interaction graph for our IRC channel39.89 KB

30 September 2009

Zak B. Elep: Off We Go...

Arrived at Manila yesterday. At least in the parts we passed through (Pasay, Makati, and Malate,) everything seems back to normal, with almost no sign of the previous storm's havoc. And nary a raindrop in sight. I'm staying at my cousin Deng's place, as usual with my Manila trips. We found it in a bit of chaos, as ondoy's flooding reached here, recalling the water being almost knee-deep. The water's gone now though, but a lot of things that were not brought up to the apartment's second floor are still wet. Hopefully they will be dry before the next storm comes in... A little history: the last time I lived in Manila was in September 2006, when Orange & Bronze got me for a 3-month contract work on a project. At the time, I was fresh off my GSoC work, and I was raring to whet my skills as a software developer. That job gave me that opportunity, as well as opportunities to meet new friends, participate in a bit of commercial software development, and get used to Manila life again. Then awhile later I got into the Morph Labs bandwagon to become its first Systems Administrator, working from Daet via telecommuting. It was here that I began the long road to unrecovery^H^H^H^H^H learning the ropes of being a sysadmin, building some cool stuff in the process as well as maintaining it. Now, I'm back here in Manila, to work for Orange & Bronze again, but this time in a more permanent position, as Systems Administrator Lead. I look forward to reconnecting with my old pals as well as meet new ones, use the valuable experience I've had over the years in my new job, and get back to the long road of unrecovery^H^H^H^H^H building more cool stuff. And I start doing that tomorrow. Good times ahead.

11 April 2009

Andrew McMillan: DAViCal 0.9.6.3 released

After far too long (too many holidays away from my keyboard :-) I've released DAViCal 0.9.6.3 and AWL 0.36. This is mostly a stability release, fixing all those little niggles that might only affect a few corner cases, but it frees me up now to concentrate on adding more functionality for a 0.9.7 release in due course. Hopefully this will be the last in the 0.9.6.x series. More information in the DAViCal release notes on the wiki. There are quite a few changes to the AWL libraries which this release depends on, but most of those changes have been driven by my Work Request Management System and Capital APMS projects, rather than from DAViCal.

6 April 2009

John Goerzen: Today

This morning started with a call from my mom. She was calling to let us know that my grandmother had apparently taken a turn for the worse, and we didn t know how much time was left. Terah, Jacob, and I got dressed and went to the hospital to visit. Grandma had been in a partial coma that left her able to hear, nod, and hold hands, but unable to speak. Apparently she came out of it overnight, and was talking and even singing with my mom and aunt some. She seemed happy, loved to hear the old German prayers she grew up with. She said some memorable things I ll have to write about later too. By the time we got there, she wasn t that active anymore, and was about like she was yesterday. She could see us, and give our hands a squeeze, maybe nod occasionally. She drifted asleep often. One time when I saw she was awake with her eyes open, I lifted up Jacob to where she could see him. Immediately her face brightened, and I saw the biggest smile on her face that I d seen in days! She smiled and waved to him, and with a small bit of coaxing, he waved back. Then she wiggled her foot, and when Jacob noticed, he wiggled his foot back at her. He s 92 years younger than her, and they still managed to communicate just fine. Grandma held my hand for awhile this morning; she loves to have a hand or two to hold. My cousin showed up for a visit, and I was going to get up to let her sit down, and when grandma felt my hand leaving, she grabbed on tighter. I went around to the other side of the bed and held the other hand. In the last couple of days, grandma s hearing has become much sharper than before, and I m pretty sure her memory has too. When it was time for us to leave and go to church, I was holding her hand, looking into her eyes, saying goodbye, and how much I loved her. I got a hand squeeze, and saw a few tears at her eyes the first I can remember seeing that. As I pulled my hand away to leave, she once again grabbed tighter and was still looking at me. I think she thought this was the last time she d see me, and didn t want the moment to pass too quickly. But that also guaranteed that I d be back in the afternoon. It was Palm Sunday in church today, and as every year, the Palm Sunday celebration ends with Bill singing I walked today where Jesus walked while Dale carries in a heavy cross. Right when Bill is singing I climbed the hill on Calvary, where on the cross He died! , Dale is climbing the steps to the stage and laying the cross there. Hardly a dry eye in the room at that. For lunch, my parents were still hosting their monthly college student get-together/home cooking event which we usually attend too. After that, I dropped off Terah and Jacob at home, and went back to the hospital. By the time I had left, grandma had 27 visitors just today. When I arrived, my great uncle (her brother) and aunt were there, along with her pastor and a deacon from her church, plus my mom and my aunt. More people came and went throughout the afternoon, and I enjoyed visiting and hearing stories about the family and grandma all afternoon. She was sometimes awake and able to look at people, and sometimes deeply asleep. I heard her say God bless you to several people. It was awhile before I had a chance to go be with her, and when I did, she was asleep, so I sat beside her bed and held her hand for quite awhile, maybe an hour, while visiting with family in the room. At one point, a nurse came in to give her some more morphine by IV. I got up to get our of her way, but the nurse said, Don t think of it; I can work around you, and what you re doing is more important than what I m doing anyway. Some of her old friends from church came by, and prayed with her in German, and recited the words to some German hymns and poems, which I think she really enjoyed. Her eyes were closed through most of it, but when they got to the end of each item, they opened, saying thank you, I believe. Eventually things calmed down, and it was time for me to head home. Grandma was awake by then, and I looked at her and said goodbye, and that I was glad I got to see her again. We gave each other a final hand squeeze, and then she let go of my hand this time. I m glad I came back for sure. They say sometimes that people that are dying feel like they need permission to die, before they will let themselves go. I wondered if grandma was feeling that way today. But then I hear that my uncle, who is driving in from 8 hours away through wintry weather, called and ask that she be given this message: Tell mom that I m coming, but if Jesus gets there first, she should go with him. That might not work on everybody, but for her, it s the best way I could think of to give her permission.

Next.

Previous.