Way back in 2003, I posted an article called CoCreate Modeling alarm clock: Beep whenever a long-running command terminates. Besides reminding me of my age (sigh), the article served a useful purpose this week, as it illustrates
a technique required to solve a problem which came up in a forum discussion
the other day.
In the article, I showed how to subscribe to events in CoCreate Modeling (aka
"PTC Creo Elements/Direct Modeling") for fun and profit.
It turns out that this technique can also be applied to solve the following problem: In customization code which is loaded into CoCreate Modeling during
startup, you want to set the user's default working directory to some default value specific to your environment or team.
You'd think that this should be trivial, as the current directory can be set using the IKIT's
sd-set-current-working-directory
API.
But when you call this function during startup (i.e. from
code in
sd_customize
, or in code loaded from there), you may find that other customization code or even CoCreate Modeling itself
changes the current directory after your code runs. This is because CoCreate Modeling remembers the directory which was current
before the user closed the last session. When you restart the application, it will try to "wake up" in precisely that working directory.
To override this behavior, here's a simple trick:
- In
sd_customize
(or, preferably, in code loaded from there), register an event handler for the SD-INTERACTIVE-EVENT
.
- This event will be fired when startup has completed and the application becomes interactive.
- In the event handler:
- Set the current working directory as you see fit
- Unregister from the event (we want one-shot behavior here)
And here is what event handler code like this would look like:
(in-package :de.clausbrod)
(use-package :oli)
(defun interactive-event-handler(&rest r)
(sd-set-current-working-directory (user-homedir-pathname))
(sd-unsubscribe-event *SD-INTERACTIVE-EVENT* 'interactive-event-handler))
(sd-subscribe-event *SD-INTERACTIVE-EVENT* 'interactive-event-handler)
This particular event handler sets the current working directory to the user's home directory, but this is of course just an example
for a reasonable default.
In more than two years of owning a Macbook, I never faced a single issue upgrading the OS. Until today when, after installing the
OS X 10.11.1, the Macbook would just hang after rebooting.
More precisely, the progress bar on the boot screen would stop at roughly two thirds of the way. So today, finally, I had to figure
out what kind of diagnosis and recovery options OS X actually provides.
The two most important tools were:
- ⌘+V during reboot: Boot in verbose mode, showing the boot messages instead of the Apple logo and progress bar. I noticed
that the boot process hung after a message saying pci pause: SDXC.
- ⌘+R during reboot: Boots into recovery mode,
which offers tools for disk and networking diagnosis, as well as
the option to open a terminal as a superuser.
Things I tried:
I was dangerously close to reinstalling the OS now, with only two aces left up my sleeves:
Single-user mode
and kernel extensions.
I had seen references to startup issues related to kernel extensions in some articles, but knew very little about them.
The first thing I tried was to temporarily disable kernel extension signing (
nvram boot-args=kext-dev-mode=1), with no
discernible effect. Fortunately, at this point I came across
Justin Silver's blog article
"OS X El Capitan 10.11.1 Hanging on Boot"
in which he describes a startup issue which looks a
lot like mine. His excellent summary contains
instructions on disabling kernel extensions using recovery mode - which helped me solve my problem!
I booted into recovery mode, opened a terminal window, and then:
$ mount -rw /
$ cd /Volumes/Macintosh\ HD
$ cd Library/Extensions
$ ls
ACS6x.kext HighPointIOP.kext
ATTOCelerityFC8.kext HighPointRR.kext
ATTOExpressSASHBA2.kext PromiseSTEX.kext
ATTOExpressSASRAID2.kext SoftRAID.kext
AX88179_178A.kext hp_io_enabler_compound.kext
ArcMSR.kext hp_io_printerclassdriver_enabler.kext
CalDigitHDProDrv.kext
Following Justin's advice, I moved all kernel extensions out of
/Library/Extensions
to disable them temporarily, and
then rebooted. Lo and behold, the login screen appeared! After a few such rounds of moving kernel extensions back to
/Library/Extensions
and rebooting, the root cause of my startup issues turned out to be those HP printer extensions.
Phew.
The HP kernel extensions had been on my system for quite some time, of course, and had never caused any problems like this before.
So there are still open questions about what exactly had happened here. To be solved on a rainy day.
Other related links:
Update January 2nd, 2016: Today it happened again - the MacBook hung at the same point during reboot. I remember
I installed an HP scanner driver roughly a week ago, and I probably did not reboot since then. The above recipe
worked for me this time again.
Update November 2016: Apparently, the printer driver reinstalls itself automatically:
See also
https://support.apple.com/en-us/HT201465. Which explains the many times I have had to re-apply the workaround described above.
I guess it is about time to look for and eliminate the root cause of all this.
I had seen that pesky message every now and then before, but today I finally got fed up with it, as I was pretty
sure that no application was running which had a legitimate business touching the external drive - at least
none of the applications I had started myself. So I set out on a quest to find the culprit.
The weapon of choice in such a case is
lsof:
sudo lsof | grep /Volumes/FOO
In my case, this produced a list of files opened by a process called
mds_store
which, apparently, is used
to produce the Spotlight search index:
mds_store 42059 root txt REG 1,6 3277 8832664
/Volumes/FOO/.Spotlight-V100/Store-V2/A75D8EF4-8412-4A13-8775-A52C20F05842/live.0.indexGroups
mds_store 42059 root txt REG 1,6 32768 8832663
/Volumes/FOO/.Spotlight-V100/Store-V2/A75D8EF4-8412-4A13-8775-A52C20F05842/live.0.indexIds
mds_store 42059 root txt REG 1,6 8192 8832666
/Volumes/FOO/.Spotlight-V100/Store-V2/A75D8EF4-8412-4A13-8775-A52C20F05842/live.0.indexTermIds
mds_store 42059 root txt REG 1,6 8192 8832668
/Volumes/FOO/.Spotlight-V100/Store-V2/A75D8EF4-8412-4A13-8775-A52C20F05842/live.0.indexPositionTable
mds_store 42059 root txt REG 1,6 8224 8832669
/Volumes/FOO/.Spotlight-V100/Store-V2/A75D8EF4-8412-4A13-8775-A52C20F05842/live.0.indexDirectory
mds_store 42059 root txt REG 1,6 1024 8832670
/Volumes/FOO/.Spotlight-V100/Store-V2/A75D8EF4-8412-4A13-8775-A52C20F05842/live.0.indexCompactDirectory
mds_store 42059 root txt REG 1,6 65536 8832671
/Volumes/FOO/.Spotlight-V100/Store-V2/A75D8EF4-8412-4A13-8775-A52C20F05842/live.0.indexArrays
mds_store 42059 root 5r DIR 1,6 2074 8832643
/Volumes/FOO/.Spotlight-V100/Store-V2/A75D8EF4-8412-4A13-8775-A52C20F05842
mds_store 42059 root 11r DIR 1,6 2074 8832643
/Volumes/FOO/.Spotlight-V100/Store-V2/A75D8EF4-8412-4A13-8775-A52C20F05842
mds_store 42059 root 47u REG 1,6 28 8832650
/Volumes/FOO/.Spotlight-V100/Store-V2/A75D8EF4-8412-4A13-8775-A52C20F05842/indexState
mds_store 42059 root 49u REG 1,6 118784 8832674
/Volumes/FOO/.Spotlight-V100/Store-V2/A75D8EF4-8412-4A13-8775-A52C20F05842/.store.db
backupd 42148 root 4w REG 1,6 1300 8832803
/Volumes/FOO/Backups.backupdb/Claus/2015-10-25-175619.inProgress/.Backup.467484979.422497.log
The fun part was that
I had explicitly configured Spotlight to ignore that particular external disk - and yet,
it was still trying to index it!
Turns out that I am not alone with this.
"Disable Spotlight on a FAT32 external drive"
provides the best summary I could find. Apparently, the type of file system on the external drive plays a role.
I was somewhat skeptical about this claim, but then, all my external drives had FAT32 file systems on them,
and so I followed the instructions in the article. I was still somewhat incredulous, as this seemed to be such a basic issue and the article is rather old.
But then, following the instructions indeed seemed to be successful, at least initially.
The magic ingredient in the sauce was to create a top-level file called
.metadata_never_index
on
the affected drive:
touch /Volumes/FOO/.metadata_never_index
I was too impatient to wait for existing instances of
mds_store
to finish their work,
and did not care about the consistency of the Spotlight index on the external drive anyway.
So I killed the
mds_store
process right away, then unplugged the drive and plugged it in again.
From there, indeed I had no issues anymore with ejecting the external disk. Hmmm...
Other related articles and discussions:
Unweigerlich lief mein gutes altes
Samsung Galaxy SII GT-I9100 voll. Es war nicht mehr zu ignorieren: Neue Apps liessen sich wegen Platzmangels unter /data
nicht mehr installieren, App-Updates schlugen aus dem gleichen Grund fehl, und bereits installierte Applikationen
wurden zickig.
Zum Beispiel zeigt die Kamera-App nach dem Start gelegentlich keine Buttons mehr an, so dass mit ihr nicht viel anzufangen war.
Ob auch das mit dem Speichermangel zu tun hat? Keine Ahnung - aber es war einfach Zeit, mal wieder aufzuräumen. Eine
Randbedingung machte die Aufgabe schwierig: Ich wollte mein Telefon dazu nicht ohne Not "rooten"...
Bevor ich mir Rootzugang verschaffe, wollte ich zuerst ein paar (vermeintlich?) einfachere Ansätze durchprobieren.
Methode 1:
SD Maid
SD Maid hatte ich vor einer Weile schon installiert und gekauft - ich kann die Applikation auch weiterempfehlen,
denn sie findet immer wieder mal beseitigenswerte Reste oder aber hilft bei der Analyse. Im vorliegenden Fall
zeigte sie an, dass von den knapp 2 GB Platz unter
/data
nur noch wenige Prozent frei waren. Ich hatte mir vorher
angelesen, dass Android-Applikationen sich normalerweise hier installieren. Man kann Applikationen zwar auch auf die SD-Karte
schieben, aber - so warnten die einschlägigen Artikel - dennoch könnte es sein, dass die verschobenen Applikationen
Teile ihrer Daten weiter unter
/data
ablegten.
Ebenfalls erwähnenswert ist in diesem Zusammenhang das kleine, aber feine
Diskusage, das
schnell einen grafischen Überblick der Speichersituation verschafft.
Methode 2:
adb shell
Vor zwei Jahren hatte ich mir Android-Entwicklergrundwissen angelesen und dabei auch das Android SDK installiert,
zu dem auch
adb
gehört. Nach Aktivieren des "USB debugging" in den Developer Options meines Telefons
entspann sich folgender, eher unbefriedigender Dialog mit meinem Telefon:
clausb:platform-tools clausb$ ./adb shell
shell@android:/ $ ls /data
opendir failed, Permission denied
"Think, McFly, think!" Eigentlich klar. Ohne Rootrechte auf dem Telefon geht halt auch mittels
adb
nicht viel. Hmpf.
(Guter Grundlagenartikel zum Thema:
http://techblogon.com/android-file-system-structure-architecture-layout-details/)
Methode 3:
SysDump
Auf dem Samsung-Telefon kann man durch Eingabe von
*#9900# auf der Telefontastatur ein verstecktes
Programm namens
SysDump starten. Offenbar dient dieses Tool der Analyse von Kernelproblemen. Es bietet
aber auch eine Option namens "Delete dumpstate/logcat", die alte Dumps und Logfiles entfernt.
Wie der
Artikel unter
http://www.guyrutenberg.com/2013/11/01/galaxy-s2-clearing-logs-on-an-unrooted-phone/
und die Diskussion unter
http://androidforums.com/threads/low-on-space-system-data-huge.278837/ zeigen,
hat diese Option schon so manchem geholfen - und so war es auch bei mir. Nach "Delete dumpstate/logcat"
waren wieder deutlich mehr als 30% des Systembereichs unter
/data
frei!
Bei der Recherche kam mir auch
Root Cleaner unter. Das ist eine
Applikation, die sich eigens auf das Aufräumen von Systemdaten spezialisiert und vermutlich deutlich über das
Löschen von Dumpfiles hinausgeht. Guter Ansatz - aber auch diese App braucht Root-Rechte.
Methode 4: Applikationen deinstallieren
Nicht originell, aber der Vollständigkeit halber muss es erwähnt werden: Natürlich fanden sich auch auf meinem
Telefon Apps, die ich so gut wie nie nutze, so dass sie verzichtbar waren.
Methode 5: Applikationen abspecken
Praktisch alle Apps legen lokal Cachedaten an und verbrauchen damit eventuell Platz in
/data
. Der Applikationsmanager
von Android bietet für jede Applikation Optionen zum Löschen lokaler App-Daten an. Besonders ausgeprägt ist der
App-Cache natürlich bei Webbrowsern wie Firefox. Hier verstecken sich die Optionen zum Löschen des Caches in
den
privacy options.
Fazit: Ein ganzer Abend war verdaddelt mit Hausmeistertätigkeiten, aber am Ende hatte
/data
wieder fast 40% freien Speicher.
Mit der Folge, dass etliche automatische App-Updates losrappelten, die schon lange sehnsüchtig darauf gewartet hatten,
dass sich ihnen der entsprechende Platz böte - und diesmal erfolgreich waren.
Hach, heute bin ich nostalgisch drauf. Diese Woche zeigte mir ein Kollege ein Stückchen Lisp-Code,
nur drei Zeilen lang, und dennoch barg es Gesprächsstoff für eine halbe Stunde. Und
einen Anlass, mit Codevarianten zu experimentieren.
Die Aufgabe des Code-Stückchens war, in CoCreate Modeling eine Baugruppe abzuklappern
und dabei zu verflachen. (Jaja, der aktuelle Produktname ist sowas wie PTC Creo Elements/Direct Modeling,
aber spätestens beim Schrägstrich nicke ich weg.) Sprich: Für jedes Element
in der Baugruppe wird ein Eintrag in der Resultatliste erzeugt, und diese Liste ist flach,
also unverschachtelt.
In CoCreate Modeling werden Objekte repräsentiert durch sogenannte
SEL_ITEMs -
Lisp-Strukturen, die für Teile, Baugruppen, Arbeitsebenenen und allerlei andere Objekte
in einem 3D-Modell stehen können.
Damit man den Lisp-Code in diesem Artikel vielleicht auch einmal in einer anderen
Lisp-Implementierung testen kann, definieren wir uns aber zunächst einmal eine extrem
eingedampfte Sparversion als eigenen Datentyp
node
:
(defstruct node
(name "" :type string)
(children nil :type list))
Das reicht, um einen einfachen Teilebaum abzubilden. Ein Knoten kann entweder ein
einfaches Teil repräsentieren - in diesem Fall hat er nur einen Namen. Wenn es sich
um eine Baugruppe handelt, hält der Knoten eine Liste von Kindknoten in
children
.
(defmethod print-object ((node node) stream)
(format stream "~A [~A] "
(node-name node)
(if (node-children node) "asm" "part")))
Damit man einen
node
halbwegs kompakt ausgeben kann, definieren wir uns
ein zur Struktur passendes generisches
print-object
. Aus der etwas langatmigen Darstellung
einer Strukturinstanz wie
#S(NODE :NAME "42" :CHILDREN (#S(NODE :NAME "p42" :CHILDREN NIL)))
wird dadurch
42 [asm]
Testbaugruppen baut man sich einfach per Strukturliteral. Beispiel:
(let ((tree #S(NODE :NAME "a1"
:CHILDREN (#S(NODE :NAME "p1")
#S(NODE :NAME "p2")
#S(NODE :NAME "a11"
:CHILDREN (#S(NODE :NAME "p11")
#S(NODE :NAME "p12")))
#S(NODE :NAME "a12"
:CHILDREN (#S(NODE :NAME "p13")
#S(NODE :NAME "p14")))))))
Mit dieser Vorbereitung können wir nun endlich des Kollegen Codeschnippsel betrachten.
Naja, eine leicht angepasste Variante davon jedenfalls:
(defun flatten-assembly-apply-nconc(node)
(cons node
(apply #'nconc (mapcar #'flatten-assembly-apply-nconc (node-children node)))))
Ruft man
flatten-assembly-apply-nconc
für die obige Testbaugruppe
(flatten-assembly-apply-nconc tree)
, erhält man
dank des von uns definierten
print-object
in der REPL in etwa folgendes:
(a1 [asm] p1 [part] p2 [part] a11 [asm] p11 [part] p12 [part] a12 [asm] p13 [part] p14 [part])
Es entsteht also in der Tat eine flache Liste - wie schön.
Sich zu verbildlichen, warum die Funktion die gewünschten Effekt hat, braucht schon einen kleinen Moment - und
vielleicht auch den einen oder anderen Blick ins Lisp-Manual, um sich der genauen Funktionsweise
von
nconc oder
mapcar zu vergewissern.
Entscheidend ist unter anderem, dass Lisp-Listen letztlich Ketten von cons-Zellen sind, deren letztes
Element auf
nil
verweist, und dass
node-children
genau solche
nil-Werte passend liefert, die
von
mapcar
und
nconc
auch brav durchgeschleust werden.
flatten-assembly-apply-nconc
setzt das "destruktive"
nconc ein, um weniger
Speicher allozieren zu müssen. Was mich gleich zu der Frage geführt hat, ob es vielleicht noch effizienter geht,
und so entstanden folgende Varianten:
(defun flatten-assembly-apply-append(node)
(cons node
(apply #'append (mapcar #'flatten-assembly-apply-append (node-children node)))))
(defun flatten-assembly-mapcan(node)
(cons node
(mapcan #'flatten-assembly-mapcan (node-children node))))
;; version using an accumulator
(defun flatten-assembly-accumulator(node &optional acc)
(cond
((null node) acc)
((listp node) (flatten-assembly-accumulator (first node) (flatten-assembly-accumulator (rest node) acc)))
((null (node-children node)) (cons node acc))
;; assembly case, i.e. a node with children
(t (cons node (flatten-assembly-accumulator (node-children node) acc)))))
Diese Varianten habe ich hintereinander in drei Lisp-Implementierungen ausgemessen, und zwar in
CLISP 2.49,
Clozure CL 1.1 und
SBCL 1.2.10. Weil SBCL sich
zumindest auf Mac OS
bei kurzläufigen Tests zickig
anstellt und keine Messdaten liefert, habe ich die jeweilige Testfunktion in einer Schleife 100000mal aufgerufen:
(let ((tree #S(NODE :NAME "a1"
:CHILDREN (#S(NODE :NAME "p1")
#S(NODE :NAME "p2")
#S(NODE :NAME "a11"
:CHILDREN (#S(NODE :NAME "p11")
#S(NODE :NAME "p12")))
#S(NODE :NAME "a12"
:CHILDREN (#S(NODE :NAME "p13")
#S(NODE :NAME "a121"
:CHILDREN (#S(NODE :NAME "a1211"
:CHILDREN (#S(NODE :NAME "p1211")))))
#S(NODE :NAME "p14")))))))
(defun run-test(function-symbol)
(gc)
(format t "~%Test function: ~A~%" (symbol-name function-symbol))
(print (time (dotimes (i 100000) (run-test-raw function-symbol)))))
)
(run-test 'flatten-assembly-apply-append)
(run-test 'flatten-assembly-apply-nconc)
(run-test 'flatten-assembly-mapcan)
(run-test 'flatten-assembly-accumulator)
Variante | Lisp-Implementierung | Laufzeit (µs) | Allokation (Bytes) |
flatten-assembly-apply-append | CLISP | 3173017 | 72000000 |
flatten-assembly-apply-nconc | CLISP | 3034901 | 56000000 |
flatten-assembly-mapcan | CLISP | 2639819 | 38400000 |
flatten-assembly-accumulator | CLISP | 4959644 | 46400000 |
flatten-assembly-apply-append | CCL | 70407 | 52800000 |
flatten-assembly-apply-nconc | CCL | 54713 | 36800000 |
flatten-assembly-mapcan | CCL | 128232 | 19200000 |
flatten-assembly-accumulator | CCL | 20997 | 19200000 |
flatten-assembly-apply-append | SBCL | 37000 | 52768224 |
flatten-assembly-apply-nconc | SBCL | 25000 | 36798464 |
flatten-assembly-mapcan | SBCL | 29000 | 19169280 |
flatten-assembly-accumulator | SBCL | 22000 | 19169280 |
Die Angaben zu Zeit- und Speicherverbrauch lieferte dabei jeweils
time.
Es gibt also durchaus signifikante Unterschiede im Speicherverbrauch. In CCL und SBCL liefert
die Variante
flatten-assembly-accumulator die beste Kombination aus Performance und
Speichersparsamkeit. Für CLISP ist dagegen
flatten-assembly-mapcan die vielversprechendste
Alternative.
Weitere Vorschläge für Varianten? Bin gespannt!
PS: Natürlich ist das hier beschriebene Problem eine Variante der Aufgabe, eine verschachtelte Liste
plattzuklopfen.
http://rosettacode.org/wiki/Flatten_a_list#Common_Lisp hält einschlägige Lösungen
hierfür parat.
PS/2: In der Lisp-Implementierung HCL, die in CoCreate Modeling verwendet wird, schneiden
flatten-assembly-apply-nconc
und
flatten-assembly-mapcan
am besten ab. Dies ist aber mit Vorbehalt
gesagt, denn in HCL musste ich den Code - mangels Compiler-Lizenz - interpretiert ablaufen lassen,
was das Performancebild vermutlich stark verfälscht.
Whenever I connect an external monitor to my MacBook Pro via a Thunderbolt/DVI adapter, the Mac
loses its wifi connection. Huh?
My setup: A MacBook Pro 2013 model with Retina display, a Thunderbolt/DVI adapter hooked up to an
external old Dell monitor. The Mac has a stable wifi connection - until I plug in the DVI adapter.
This problem annoyed me for quite some time, up to the point I did not even use the external monitor
anymore. Today, it happened to me again, and I decided to finally try and tackle the issue.
I found
this discussion
titled "My wifi drops when I plug in an external monitor through the thunderbolt port". In this discussion,
a workaround is suggested - which is changing the wifi channel!
And indeed, this helped! Before, my router was using channel 4. I reconfigured the router
to select an appropriate channel automatically, and now it is on channel 1, as confirmed by the
Wireless Diagnostics tool.
Older topics...
Add a new blog entry.