Date: Tue, 05 Jun 2012 06:00:06 +0000 From: gmiller@FreeBSD.org To: svn-soc-all@FreeBSD.org Subject: socsvn commit: r237113 - soc2012/gmiller/locking-head/lib/libthr/thread Message-ID: <20120605060006.EDA831065670@hub.freebsd.org>
next in thread | raw e-mail | index | archive | help
Author: gmiller Date: Tue Jun 5 06:00:06 2012 New Revision: 237113 URL: http://svnweb.FreeBSD.org/socsvn/?view=rev&rev=237113 Log: Add time parameters to lock profiling functions to track blocking times on contested locks. Modified: soc2012/gmiller/locking-head/lib/libthr/thread/thr_mutex.c soc2012/gmiller/locking-head/lib/libthr/thread/thr_private.h soc2012/gmiller/locking-head/lib/libthr/thread/thr_profile.c soc2012/gmiller/locking-head/lib/libthr/thread/thr_pspinlock.c soc2012/gmiller/locking-head/lib/libthr/thread/thr_rwlock.c Modified: soc2012/gmiller/locking-head/lib/libthr/thread/thr_mutex.c ============================================================================== --- soc2012/gmiller/locking-head/lib/libthr/thread/thr_mutex.c Tue Jun 5 05:16:04 2012 (r237112) +++ soc2012/gmiller/locking-head/lib/libthr/thread/thr_mutex.c Tue Jun 5 06:00:06 2012 (r237113) @@ -339,19 +339,23 @@ struct pthread_mutex *m = *mutex; uint32_t id; int ret; +#ifdef LOCK_PROFILING + struct timespec waittime; + bzero(&waittime, sizeof(waittime)); +#endif id = TID(curthread); if (m->m_flags & PMUTEX_FLAG_PRIVATE) THR_CRITICAL_ENTER(curthread); ret = _thr_umutex_trylock(&m->m_lock, id); if (__predict_true(ret == 0)) { ENQUEUE_MUTEX(curthread, m); - MUTEX_OBTAIN_SUCCESS(m); + MUTEX_OBTAIN_SUCCESS(m, &waittime); } else if (m->m_owner == curthread) { ret = mutex_self_trylock(m _PROFILE_PASS); - MUTEX_OBTAIN_FAILED(m); + MUTEX_OBTAIN_FAILED(m, &waittime); } else { - MUTEX_OBTAIN_FAILED(m); + MUTEX_OBTAIN_FAILED(m, &waittime); } if (ret && (m->m_flags & PMUTEX_FLAG_PRIVATE)) @@ -387,11 +391,15 @@ uint32_t id, owner; int count; int ret; +#ifdef LOCK_PROFILING + struct timespec waittime; + bzero(&waittime, sizeof(waittime)); +#endif if (m->m_owner == curthread) return mutex_self_lock(m, abstime _PROFILE_PASS); - MUTEX_OBTAIN_FAILED(m); + MUTEX_OBTAIN_FAILED(m, &waittime); id = TID(curthread); /* @@ -446,7 +454,7 @@ done: if (ret == 0) { ENQUEUE_MUTEX(curthread, m); - MUTEX_OBTAIN_SUCCESS(m); + MUTEX_OBTAIN_SUCCESS(m, &waittime); } return (ret); @@ -458,14 +466,18 @@ { struct pthread *curthread = _get_curthread(); int ret; +#ifdef LOCK_PROFILING + struct timespec waittime; + bzero(&waittime, sizeof(waittime)); +#endif if (!cvattach && m->m_flags & PMUTEX_FLAG_PRIVATE) THR_CRITICAL_ENTER(curthread); if (_thr_umutex_trylock2(&m->m_lock, TID(curthread)) == 0) { ENQUEUE_MUTEX(curthread, m); ret = 0; - MUTEX_OBTAIN_SUCCESS(m); + MUTEX_OBTAIN_SUCCESS(m, &waittime); } else { ret = mutex_lock_sleep(curthread, m, abstime _PROFILE_PASS); } @@ -600,7 +612,11 @@ mutex_self_trylock(struct pthread_mutex *m _PROFILE_PARMS) { int ret; +#ifdef LOCK_PROFILING + struct timespec waittime; + bzero(&waittime, sizeof(waittime)); +#endif switch (PMUTEX_TYPE(m->m_flags)) { case PTHREAD_MUTEX_ERRORCHECK: case PTHREAD_MUTEX_NORMAL: @@ -622,9 +638,9 @@ } if (ret == 0) { - MUTEX_OBTAIN_SUCCESS(m); + MUTEX_OBTAIN_SUCCESS(m, &waittime); } else { - MUTEX_OBTAIN_FAILED(m); + MUTEX_OBTAIN_FAILED(m, &waittime); } return (ret); @@ -636,11 +652,15 @@ { struct timespec ts1, ts2; int ret; +#ifdef LOCK_PROFILING + struct timespec waittime; + bzero(&waittime, sizeof(waittime)); +#endif switch (PMUTEX_TYPE(m->m_flags)) { case PTHREAD_MUTEX_ERRORCHECK: case PTHREAD_MUTEX_ADAPTIVE_NP: - MUTEX_OBTAIN_FAILED(m); + MUTEX_OBTAIN_FAILED(m, &waittime); if (abstime) { if (abstime->tv_sec < 0 || abstime->tv_nsec < 0 || @@ -666,7 +686,7 @@ * What SS2 define as a 'normal' mutex. Intentionally * deadlock on attempts to get a lock you already own. */ - MUTEX_OBTAIN_FAILED(m); + MUTEX_OBTAIN_FAILED(m, &waittime); ret = 0; if (abstime) { @@ -694,7 +714,7 @@ ret = 0; } else { ret = EAGAIN; - MUTEX_OBTAIN_FAILED(m); + MUTEX_OBTAIN_FAILED(m, &waittime); } break; @@ -704,7 +724,7 @@ } if (ret == 0) { - MUTEX_OBTAIN_SUCCESS(m); + MUTEX_OBTAIN_SUCCESS(m, &waittime); } return (ret); @@ -716,7 +736,11 @@ struct pthread *curthread = _get_curthread(); uint32_t id; int defered; +#ifdef LOCK_PROFILING + struct timespec waittime; + bzero(&waittime, sizeof(waittime)); +#endif if (__predict_false(m <= THR_MUTEX_DESTROYED)) { if (m == THR_MUTEX_DESTROYED) return (EINVAL); @@ -736,10 +760,10 @@ m->m_count--; if (m->m_count == 0) { - MUTEX_RELEASE(m); + MUTEX_RELEASE(m, &waittime); } } else { - MUTEX_RELEASE(m); + MUTEX_RELEASE(m, &waittime); if ((m->m_flags & PMUTEX_FLAG_DEFERED) != 0) { defered = 1; Modified: soc2012/gmiller/locking-head/lib/libthr/thread/thr_private.h ============================================================================== --- soc2012/gmiller/locking-head/lib/libthr/thread/thr_private.h Tue Jun 5 05:16:04 2012 (r237112) +++ soc2012/gmiller/locking-head/lib/libthr/thread/thr_private.h Tue Jun 5 06:00:06 2012 (r237113) @@ -743,27 +743,41 @@ int _mutex_owned(struct pthread *, const struct pthread_mutex *) __hidden; int _mutex_reinit(pthread_mutex_t *) __hidden; void _mutex_fork(struct pthread *curthread) __hidden; -void _mutex_obtain_failed(struct pthread_mutex *, const char *, int) +void _mutex_obtain_failed(struct pthread_mutex *, + struct timespec *waittime, const char *, int) __hidden; -void _mutex_obtain_success(struct pthread_mutex *, const char *, int) +void _mutex_obtain_success(struct pthread_mutex *, + struct timespec *waittime, const char *, int) __hidden; void _lock_profile_exit_thread(struct pthread *curthread) __hidden; -void _rwlock_obtain_read_success(struct pthread_rwlock *, const char *file, - int line) __hidden; -void _rwlock_obtain_read_failed(struct pthread_rwlock *, const char *file, - int line) __hidden; -void _rwlock_obtain_write_success(struct pthread_rwlock *, const char *file, - int line) __hidden; -void _rwlock_obtain_write_failed(struct pthread_rwlock *, const char *file, - int line) __hidden; -void _rwlock_release_read(struct pthread_rwlock *) __hidden; -void _rwlock_release_write(struct pthread_rwlock *) __hidden; -void _mutex_release(struct pthread_mutex *) __hidden; -void _spin_obtain_success(struct pthread_spinlock *, const char *file, - int line) __hidden; -void _spin_obtain_failed(struct pthread_spinlock *, const char *file, - int line) __hidden; -void _spin_release(struct pthread_spinlock *) __hidden; +void _rwlock_obtain_read_success(struct pthread_rwlock *, + struct timespec *waittime, + const char *file, int line) + __hidden; +void _rwlock_obtain_read_failed(struct pthread_rwlock *, + struct timespec *waittime, + const char *file, int line) + __hidden; +void _rwlock_obtain_write_success(struct pthread_rwlock *, + struct timespec *waittime, + const char *file, int line) + __hidden; +void _rwlock_obtain_write_failed(struct pthread_rwlock *, + struct timespec *waittime, + const char *file, int line) + __hidden; +void _rwlock_release_read(struct pthread_rwlock *, struct timespec *) + __hidden; +void _rwlock_release_write(struct pthread_rwlock *, struct timespec *) + __hidden; +void _mutex_release(struct pthread_mutex *, struct timespec *) __hidden; +void _spin_obtain_success(struct pthread_spinlock *, + struct timespec *waittime, + const char *file, int line) __hidden; +void _spin_obtain_failed(struct pthread_spinlock *, + struct timespec *waittime, + const char *file, int line) __hidden; +void _spin_release(struct pthread_spinlock *, struct timespec *) __hidden; void _libpthread_init(struct pthread *) __hidden; struct pthread *_thr_alloc(struct pthread *) __hidden; void _thread_exit(const char *, int, const char *) __hidden __dead2; @@ -826,43 +840,43 @@ #define LOCK_PROFILE_EXIT_THREAD(t) \ _lock_profile_exit_thread(t) -#define MUTEX_OBTAIN_SUCCESS(m) \ - _mutex_obtain_success(m, file, line) -#define MUTEX_OBTAIN_FAILED(m) \ - _mutex_obtain_failed(m, file, line) -#define MUTEX_RELEASE(m) _mutex_release(m) -#define RWLOCK_OBTAIN_READ_SUCCESS(l) \ - _rwlock_obtain_read_success(l, file, line) -#define RWLOCK_OBTAIN_READ_FAILED(l) \ - _rwlock_obtain_read_failed(l, file, line) -#define RWLOCK_OBTAIN_WRITE_SUCCESS(l) \ - _rwlock_obtain_write_success(l, file, line) -#define RWLOCK_OBTAIN_WRITE_FAILED(l) \ - _rwlock_obtain_write_failed(l, file, line) -#define RWLOCK_RELEASE_READ(l) _rwlock_release_read(l) -#define RWLOCK_RELEASE_WRITE(l) _rwlock_release_write(l) -#define SPIN_OBTAIN_SUCCESS(s) \ - _spin_obtain_success(s, file, line) -#define SPIN_OBTAIN_FAILED(s) \ - _spin_obtain_failed(s, file, line) -#define SPIN_RELEASE(s) \ - _spin_release(s) +#define MUTEX_OBTAIN_SUCCESS(m, ts) \ + _mutex_obtain_success(m, ts, file, line) +#define MUTEX_OBTAIN_FAILED(m, ts) \ + _mutex_obtain_failed(m, ts, file, line) +#define MUTEX_RELEASE(m, ts) _mutex_release(m, ts) +#define RWLOCK_OBTAIN_READ_SUCCESS(l, ts) \ + _rwlock_obtain_read_success(l, ts, file, line) +#define RWLOCK_OBTAIN_READ_FAILED(l, ts) \ + _rwlock_obtain_read_failed(l, ts, file, line) +#define RWLOCK_OBTAIN_WRITE_SUCCESS(l, ts) \ + _rwlock_obtain_write_success(l, ts, file, line) +#define RWLOCK_OBTAIN_WRITE_FAILED(l, ts) \ + _rwlock_obtain_write_failed(l, ts, file, line) +#define RWLOCK_RELEASE_READ(l, ts) _rwlock_release_read(l, ts) +#define RWLOCK_RELEASE_WRITE(l, ts) _rwlock_release_write(l, ts) +#define SPIN_OBTAIN_SUCCESS(s, ts) \ + _spin_obtain_success(s, ts, file, line) +#define SPIN_OBTAIN_FAILED(s, ts) \ + _spin_obtain_failed(s, ts, file, line) +#define SPIN_RELEASE(s, ts) \ + _spin_release(s, ts) #else -#define LOCK_PROFILE_EXIT_THREAD(t) do { } while (0) -#define MUTEX_OBTAIN_SUCCESS(m) do { } while (0) -#define MUTEX_OBTAIN_FAILED(m) do { } while (0) -#define MUTEX_RELEASE(m) do { } while (0) -#define RWLOCK_OBTAIN_READ_SUCCESS(l) do { } while (0) -#define RWLOCK_OBTAIN_READ_FAILED(l) do { } while (0) -#define RWLOCK_OBTAIN_WRITE_SUCCESS(l) do { } while (0) -#define RWLOCK_OBTAIN_WRITE_FAILED(l) do { } while (0) -#define RWLOCK_RELEASE_READ(l) do { } while (0) -#define RWLOCK_RELEASE_WRITE(l) do { } while (0) -#define SPIN_OBTAIN_SUCCESS(s) do { } while (0) -#define SPIN_OBTAIN_FAILED(s) do { } while (0) -#define SPIN_RELEASE(s) do { } while (0) +#define LOCK_PROFILE_EXIT_THREAD(t) do { } while (0) +#define MUTEX_OBTAIN_SUCCESS(m, ts) do { } while (0) +#define MUTEX_OBTAIN_FAILED(m, ts) do { } while (0) +#define MUTEX_RELEASE(m, ts) do { } while (0) +#define RWLOCK_OBTAIN_READ_SUCCESS(l, ts) do { } while (0) +#define RWLOCK_OBTAIN_READ_FAILED(l, ts) do { } while (0) +#define RWLOCK_OBTAIN_WRITE_SUCCESS(l, ts) do { } while (0) +#define RWLOCK_OBTAIN_WRITE_FAILED(l, ts) do { } while (0) +#define RWLOCK_RELEASE_READ(l, ts) do { } while (0) +#define RWLOCK_RELEASE_WRITE(l, ts) do { } while (0) +#define SPIN_OBTAIN_SUCCESS(s, ts) do { } while (0) +#define SPIN_OBTAIN_FAILED(s, ts) do { } while (0) +#define SPIN_RELEASE(s, ts) do { } while (0) #endif Modified: soc2012/gmiller/locking-head/lib/libthr/thread/thr_profile.c ============================================================================== --- soc2012/gmiller/locking-head/lib/libthr/thread/thr_profile.c Tue Jun 5 05:16:04 2012 (r237112) +++ soc2012/gmiller/locking-head/lib/libthr/thread/thr_profile.c Tue Jun 5 06:00:06 2012 (r237113) @@ -30,17 +30,19 @@ #include "thr_private.h" void -_mutex_obtain_success(struct pthread_mutex *m, const char *file, int line) +_mutex_obtain_success(struct pthread_mutex *m, struct timespec *waittime, + const char *file, int line) { } void -_mutex_obtain_failed(struct pthread_mutex *m, const char *file, int line) +_mutex_obtain_failed(struct pthread_mutex *m, struct timespec *waittime, + const char *file, int line) { } void -_mutex_release(struct pthread_mutex *m) +_mutex_release(struct pthread_mutex *m, struct timespec *waittime) { } @@ -50,51 +52,57 @@ } void -_rwlock_obtain_read_success(struct pthread_rwlock *l, const char *file, +_rwlock_obtain_read_success(struct pthread_rwlock *l, + struct timespec *waittime, const char *file, int line) { } void -_rwlock_obtain_read_failed(struct pthread_rwlock *l, const char *file, +_rwlock_obtain_read_failed(struct pthread_rwlock *l, + struct timespec *waittime, const char *file, int line) { } void -_rwlock_obtain_write_success(struct pthread_rwlock *l, const char *file, +_rwlock_obtain_write_success(struct pthread_rwlock *l, + struct timespec *waittime, const char *file, int line) { } void -_rwlock_obtain_write_failed(struct pthread_rwlock *l, const char *file, +_rwlock_obtain_write_failed(struct pthread_rwlock *l, + struct timespec *watitime, const char *file, int line) { } void -_rwlock_release_read(struct pthread_rwlock *l) +_rwlock_release_read(struct pthread_rwlock *l, struct timespec *waittime) { } void -_rwlock_release_write(struct pthread_rwlock *l) +_rwlock_release_write(struct pthread_rwlock *l, struct timespec *waittime) { } void -_spin_obtain_success(struct pthread_spinlock *s, const char *file, int line) +_spin_obtain_success(struct pthread_spinlock *s, struct timespec *waittime, + const char *file, int line) { } void -_spin_obtain_failed(struct pthread_spinlock *s, const char *file, int line) +_spin_obtain_failed(struct pthread_spinlock *s, struct timespec *waittime, + const char *file, int line) { } void -_spin_release(struct pthread_spinlock *s) +_spin_release(struct pthread_spinlock *s, struct timespec *waittime) { } Modified: soc2012/gmiller/locking-head/lib/libthr/thread/thr_pspinlock.c ============================================================================== --- soc2012/gmiller/locking-head/lib/libthr/thread/thr_pspinlock.c Tue Jun 5 05:16:04 2012 (r237112) +++ soc2012/gmiller/locking-head/lib/libthr/thread/thr_pspinlock.c Tue Jun 5 06:00:06 2012 (r237113) @@ -101,15 +101,19 @@ struct pthread *curthread = _get_curthread(); struct pthread_spinlock *lck; int ret; +#ifdef LOCK_PROFILING + struct timespec waittime; + bzero(&waittime, sizeof(waittime)); +#endif if (lock == NULL || (lck = *lock) == NULL) ret = EINVAL; else { ret = THR_UMUTEX_TRYLOCK(curthread, &lck->s_lock); if (ret == 0) { - SPIN_OBTAIN_SUCCESS(lck); + SPIN_OBTAIN_SUCCESS(lck, &waittime); } else { - SPIN_OBTAIN_FAILED(lck); + SPIN_OBTAIN_FAILED(lck, &waittime); } } @@ -133,6 +137,9 @@ int ret, count; #ifdef LOCK_PROFILING int spin_needed = 0; + struct timespec waittime; + + bzero(&waittime, sizeof(waittime)); #endif if (lock == NULL || (lck = *lock) == NULL) ret = EINVAL; @@ -142,7 +149,7 @@ #ifdef LOCK_PROFILING if (!spin_needed) { spin_needed = 1; - SPIN_OBTAIN_FAILED(lck); + SPIN_OBTAIN_FAILED(lck, &waittime); } #endif while (lck->s_lock.m_owner) { @@ -159,7 +166,7 @@ } } ret = 0; - SPIN_OBTAIN_SUCCESS(lck); + SPIN_OBTAIN_SUCCESS(lck, &waittime); } return (ret); @@ -171,13 +178,17 @@ struct pthread *curthread = _get_curthread(); struct pthread_spinlock *lck; int ret; +#ifdef LOCK_PROFILING + struct timespec waittime; + bzero(&waittime, sizeof(waittime)); +#endif if (lock == NULL || (lck = *lock) == NULL) ret = EINVAL; else { ret = THR_UMUTEX_UNLOCK(curthread, &lck->s_lock); if (ret == 0) { - SPIN_RELEASE(lck); + SPIN_RELEASE(lck, &waittime); } } return (ret); Modified: soc2012/gmiller/locking-head/lib/libthr/thread/thr_rwlock.c ============================================================================== --- soc2012/gmiller/locking-head/lib/libthr/thread/thr_rwlock.c Tue Jun 5 05:16:04 2012 (r237112) +++ soc2012/gmiller/locking-head/lib/libthr/thread/thr_rwlock.c Tue Jun 5 06:00:06 2012 (r237113) @@ -150,7 +150,11 @@ pthread_rwlock_t prwlock; int flags; int ret; +#ifdef LOCK_PROFILING + struct timespec waittime; + bzero(&waittime, sizeof(waittime)); +#endif CHECK_AND_INIT_RWLOCK if (curthread->rdlock_count) { @@ -179,12 +183,12 @@ if (ret == 0) { curthread->rdlock_count++; - RWLOCK_OBTAIN_READ_SUCCESS(*rwlock); + RWLOCK_OBTAIN_READ_SUCCESS(*rwlock, &waittime); return (ret); } - RWLOCK_OBTAIN_READ_FAILED(*rwlock); + RWLOCK_OBTAIN_READ_FAILED(*rwlock, &waittime); if (__predict_false(abstime && (abstime->tv_nsec >= 1000000000 || abstime->tv_nsec < 0))) @@ -204,7 +208,7 @@ } if (ret == 0) { curthread->rdlock_count++; - RWLOCK_OBTAIN_READ_SUCCESS(*rwlock); + RWLOCK_OBTAIN_READ_SUCCESS(*rwlock, &waittime); } return (ret); @@ -260,6 +264,11 @@ pthread_rwlock_t prwlock; int flags; int ret; +#ifdef LOCK_PROFILING + struct timespec waittime; + + bzero(&waittime, sizeof(waittime)); +#endif CHECK_AND_INIT_RWLOCK @@ -284,9 +293,9 @@ ret = _thr_rwlock_tryrdlock(&prwlock->lock, flags); if (ret == 0) { curthread->rdlock_count++; - RWLOCK_OBTAIN_READ_SUCCESS(*rwlock); + RWLOCK_OBTAIN_READ_SUCCESS(*rwlock, &waittime); } else { - RWLOCK_OBTAIN_READ_FAILED(*rwlock); + RWLOCK_OBTAIN_READ_FAILED(*rwlock, &waittime); } return (ret); } @@ -306,15 +315,20 @@ struct pthread *curthread = _get_curthread(); pthread_rwlock_t prwlock; int ret; +#ifdef LOCK_PROFILING + struct timespec waittime; + + bzero(&waittime, sizeof(waittime)); +#endif CHECK_AND_INIT_RWLOCK ret = _thr_rwlock_trywrlock(&prwlock->lock); if (ret == 0) { prwlock->owner = curthread; - RWLOCK_OBTAIN_WRITE_SUCCESS(*rwlock); + RWLOCK_OBTAIN_WRITE_SUCCESS(*rwlock, &waittime); } else { - RWLOCK_OBTAIN_WRITE_FAILED(*rwlock); + RWLOCK_OBTAIN_WRITE_FAILED(*rwlock, &waittime); } return (ret); } @@ -326,6 +340,11 @@ struct pthread *curthread = _get_curthread(); pthread_rwlock_t prwlock; int ret; +#ifdef LOCK_PROFILING + struct timespec waittime; + + bzero(&waittime, sizeof(waittime)); +#endif CHECK_AND_INIT_RWLOCK @@ -337,12 +356,12 @@ if (ret == 0) { prwlock->owner = curthread; - RWLOCK_OBTAIN_WRITE_SUCCESS(*rwlock); + RWLOCK_OBTAIN_WRITE_SUCCESS(*rwlock, &waittime); return (ret); } - RWLOCK_OBTAIN_WRITE_FAILED(*rwlock); + RWLOCK_OBTAIN_WRITE_FAILED(*rwlock, &waittime); if (__predict_false(abstime && (abstime->tv_nsec >= 1000000000 || abstime->tv_nsec < 0))) @@ -368,9 +387,9 @@ } if (ret == 0) { - RWLOCK_OBTAIN_WRITE_SUCCESS(*rwlock); + RWLOCK_OBTAIN_WRITE_SUCCESS(*rwlock, &waittime); } else { - RWLOCK_OBTAIN_WRITE_FAILED(*rwlock); + RWLOCK_OBTAIN_WRITE_FAILED(*rwlock, &waittime); } return (ret); @@ -416,7 +435,11 @@ pthread_rwlock_t prwlock; int ret; int32_t state; +#ifdef LOCK_PROFILING + struct timespec waittime; + bzero(&waittime, sizeof(waittime)); +#endif prwlock = *rwlock; if (__predict_false(prwlock <= THR_RWLOCK_DESTROYED)) @@ -432,9 +455,9 @@ ret = _thr_rwlock_unlock(&prwlock->lock); if (ret == 0 && (state & URWLOCK_WRITE_OWNER) == 0) { curthread->rdlock_count--; - RWLOCK_RELEASE_READ(*rwlock); + RWLOCK_RELEASE_READ(*rwlock, &waittime); } else if (ret == 0) { - RWLOCK_RELEASE_WRITE(*rwlock); + RWLOCK_RELEASE_WRITE(*rwlock, &waittime); } return (ret);
Want to link to this message? Use this URL: <https://mail-archive.FreeBSD.org/cgi/mid.cgi?20120605060006.EDA831065670>